kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/vfs/vfs.go (about) 1 /* 2 * Copyright 2015 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package vfs defines a generic file system interface commonly used by Kythe 18 // libraries. 19 package vfs // import "kythe.io/kythe/go/platform/vfs" 20 21 import ( 22 "context" 23 "errors" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "regexp" 29 ) 30 31 // globPattern is used by globWalker to escape glob special characters. 32 var globPattern = regexp.MustCompile(`[[*?\\]`) 33 34 // ErrNotSupported is returned for all unsupported VFS operations. 35 var ErrNotSupported = errors.New("operation not supported") 36 37 // Interface is a virtual file system interface for reading and writing files. 38 // It is used to wrap the normal os package functions so that other file storage 39 // implementations be used in lieu. For instance, there could be 40 // implementations for cloud storage back-ends or databases. Depending on the 41 // implementation, the Writer methods can be unsupported and always return 42 // ErrNotSupported. 43 type Interface interface { 44 Reader 45 Writer 46 } 47 48 // TempFile composes io.WriteCloser and access to its "name". For 49 // file-based implementations, this should be the full path to the full. 50 type TempFile interface { 51 io.WriteCloser 52 Name() string 53 } 54 55 // Reader is a virtual file system interface for reading files. 56 type Reader interface { 57 // Stat returns file status information for path, as os.Stat. 58 Stat(ctx context.Context, path string) (os.FileInfo, error) 59 60 // Open opens an existing file for reading, as os.Open. 61 Open(ctx context.Context, path string) (FileReader, error) 62 63 // Glob returns all the paths matching the specified glob pattern, as 64 // filepath.Glob. 65 Glob(ctx context.Context, glob string) ([]string, error) 66 } 67 68 // Walker is a virtual file system interface for traversing directories. 69 type Walker interface { 70 // Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root. 71 // See filepath.Walk for more details. 72 Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error 73 } 74 75 // Writer is a virtual file system interface for writing files. 76 type Writer interface { 77 // MkdirAll recursively creates the specified directory path with the given 78 // permissions, as os.MkdirAll. 79 MkdirAll(ctx context.Context, path string, mode os.FileMode) error 80 81 // Create creates a new file for writing, as os.Create. 82 Create(ctx context.Context, path string) (io.WriteCloser, error) 83 84 // CreateTempFile creates a new temp file returning a TempFile. The 85 // name of the file is constructed from dir pattern and per 86 // ioutil.TempFile: 87 // The filename is generated by taking pattern and adding a random 88 // string to the end. If pattern includes a "*", the random string 89 // replaces the last "*". If dir is the empty string, CreateTempFile 90 // uses an unspecified default directory. 91 CreateTempFile(ctx context.Context, dir, pattern string) (TempFile, error) 92 93 // Rename renames oldPath to newPath, as os.Rename, overwriting newPath if 94 // it exists. 95 Rename(ctx context.Context, oldPath, newPath string) error 96 97 // Remove deletes the file specified by path, as os.Remove. 98 Remove(ctx context.Context, path string) error 99 } 100 101 // FileReader composes interfaces from io that readable files from the vfs must 102 // implement. 103 type FileReader interface { 104 io.ReadCloser 105 io.ReaderAt 106 io.Seeker 107 } 108 109 // Default is the global default VFS used by Kythe libraries that wish to access 110 // the file system. This is usually the LocalFS and should only be changed in 111 // very specialized cases (i.e. don't change it). 112 var Default Interface = LocalFS{} 113 114 // ReadFile is the equivalent of ioutil.ReadFile using the Default VFS. 115 func ReadFile(ctx context.Context, filename string) ([]byte, error) { 116 f, err := Open(ctx, filename) 117 if err != nil { 118 return nil, err 119 } 120 defer f.Close() // ignore errors 121 return ioutil.ReadAll(f) 122 } 123 124 // Stat returns file status information for path, using the Default VFS. 125 func Stat(ctx context.Context, path string) (os.FileInfo, error) { return Default.Stat(ctx, path) } 126 127 // MkdirAll recursively creates the specified directory path with the given 128 // permissions, using the Default VFS. 129 func MkdirAll(ctx context.Context, path string, mode os.FileMode) error { 130 return Default.MkdirAll(ctx, path, mode) 131 } 132 133 // Open opens an existing file for reading, using the Default VFS. 134 func Open(ctx context.Context, path string) (FileReader, error) { return Default.Open(ctx, path) } 135 136 // Create creates a new file for writing, using the Default VFS. 137 func Create(ctx context.Context, path string) (io.WriteCloser, error) { 138 return Default.Create(ctx, path) 139 } 140 141 // CreateTempFile creates a new TempFile, using the Default VFS. 142 func CreateTempFile(ctx context.Context, dir, pattern string) (TempFile, error) { 143 return Default.CreateTempFile(ctx, dir, pattern) 144 } 145 146 // Rename renames oldPath to newPath, using the Default VFS, overwriting newPath 147 // if it exists. 148 func Rename(ctx context.Context, oldPath, newPath string) error { 149 return Default.Rename(ctx, oldPath, newPath) 150 } 151 152 // Remove deletes the file specified by path, using the Default VFS. 153 func Remove(ctx context.Context, path string) error { return Default.Remove(ctx, path) } 154 155 // Glob returns all the paths matching the specified glob pattern, using the 156 // Default VFS. 157 func Glob(ctx context.Context, glob string) ([]string, error) { return Default.Glob(ctx, glob) } 158 159 // Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root. 160 // See filepath.Walk for more details. 161 func Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error { 162 return NewWalker(Default).Walk(ctx, root, walkFn) 163 } 164 165 // NewWalker returns a Walker instance over the provided reader. 166 func NewWalker(r Reader) Walker { 167 w, ok := r.(Walker) 168 if ok { 169 return w 170 } 171 return &globWalker{r} 172 } 173 174 // LocalFS implements the VFS interface using the standard Go library. 175 type LocalFS struct{} 176 177 // Stat implements part of the VFS interface. 178 func (LocalFS) Stat(_ context.Context, path string) (os.FileInfo, error) { 179 return os.Stat(path) 180 } 181 182 // MkdirAll implements part of the VFS interface. 183 func (LocalFS) MkdirAll(_ context.Context, path string, mode os.FileMode) error { 184 return os.MkdirAll(path, mode) 185 } 186 187 // Open implements part of the VFS interface. 188 func (LocalFS) Open(_ context.Context, path string) (FileReader, error) { 189 if path == "-" { 190 return stdinWrapper{os.Stdin}, nil 191 } 192 return os.Open(path) 193 } 194 195 // Create implements part of the VFS interface. 196 func (LocalFS) Create(_ context.Context, path string) (io.WriteCloser, error) { 197 return os.Create(path) 198 } 199 200 // CreateTempFile implements part of the VFS interface. 201 func (LocalFS) CreateTempFile(_ context.Context, dir, pattern string) (TempFile, error) { 202 return ioutil.TempFile(dir, pattern) 203 } 204 205 // Rename implements part of the VFS interface. 206 func (LocalFS) Rename(_ context.Context, oldPath, newPath string) error { 207 return os.Rename(oldPath, newPath) 208 } 209 210 // Remove implements part of the VFS interface. 211 func (LocalFS) Remove(_ context.Context, path string) error { 212 return os.Remove(path) 213 } 214 215 // Glob implements part of the VFS interface. 216 func (LocalFS) Glob(_ context.Context, glob string) ([]string, error) { 217 return filepath.Glob(glob) 218 } 219 220 // Walk implements part of the VFS interface. 221 func (LocalFS) Walk(_ context.Context, root string, walkFn filepath.WalkFunc) error { 222 return filepath.Walk(root, walkFn) 223 } 224 225 // UnsupportedWriter implements the Writer interface methods with stubs that 226 // always return ErrNotSupported. 227 type UnsupportedWriter struct{ Reader } 228 229 // Create implements part of Writer interface. It is not supported. 230 func (UnsupportedWriter) Create(_ context.Context, _ string) (io.WriteCloser, error) { 231 return nil, ErrNotSupported 232 } 233 234 // CreateTempFile implements part of the VFS interface. It is not supported. 235 func (UnsupportedWriter) CreateTempFile(_ context.Context, dir, pattern string) (TempFile, error) { 236 return nil, ErrNotSupported 237 } 238 239 // MkdirAll implements part of Writer interface. It is not supported. 240 func (UnsupportedWriter) MkdirAll(_ context.Context, _ string, _ os.FileMode) error { 241 return ErrNotSupported 242 } 243 244 // Rename implements part of Writer interface. It is not supported. 245 func (UnsupportedWriter) Rename(_ context.Context, _, _ string) error { return ErrNotSupported } 246 247 // Remove implements part of Writer interface. It is not supported. 248 func (UnsupportedWriter) Remove(_ context.Context, _ string) error { return ErrNotSupported } 249 250 // UnseekableFileReader implements the io.Seeker and io.ReaderAt at portion of 251 // FileReader with stubs that always return ErrNotSupported. 252 type UnseekableFileReader struct { 253 io.ReadCloser 254 } 255 256 // ReadAt implements io.ReaderAt interface. It is not supported. 257 func (UnseekableFileReader) ReadAt([]byte, int64) (int, error) { 258 return 0, ErrNotSupported 259 } 260 261 // Seek implements io.Seeker interface. It is not supported. 262 func (UnseekableFileReader) Seek(int64, int) (int64, error) { 263 return 0, ErrNotSupported 264 } 265 266 // stdinWrapper is similar in purpose to ioutil.NopCloser, but allows access to 267 // other os.File methods that implement FileReader rather than restricting to 268 // just ioutil.ReadCloser. 269 type stdinWrapper struct { 270 *os.File 271 } 272 273 func (stdinWrapper) Close() error { 274 return nil 275 } 276 277 // globWalker wraps a Reader interface using Glob and Stat to implement Walk. 278 type globWalker struct { 279 r Reader 280 } 281 282 // escapeGlob escapes glob special characters. 283 func escapeGlob(path string) string { 284 return globPattern.ReplaceAllString(path, `\$0`) 285 } 286 287 // Walk implements the Walker interface by delegating to Glob and Stat. 288 func (gw *globWalker) Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error { 289 info, err := gw.r.Stat(ctx, root) 290 if err != nil { 291 err = walkFn(root, nil, err) 292 } else { 293 err = gw.walk(ctx, root, info, walkFn) 294 } 295 if err == filepath.SkipDir { 296 return nil 297 } 298 return err 299 300 } 301 302 // walk recusively descends path using vfs.Glob, calling walkFn on the results. 303 func (gw *globWalker) walk(ctx context.Context, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { 304 if !info.IsDir() { 305 return walkFn(path, info, nil) 306 } 307 308 names, err := gw.r.Glob(ctx, filepath.Join(escapeGlob(path), "*")) 309 userErr := walkFn(path, info, err) 310 // If err != nil, walk can't walk into this directory. 311 // userErr != nil means walkFn want walk to skip this directory or stop walking. 312 // Therefore, if one of err and userErr isn't nil, walk will return. 313 if err != nil || userErr != nil { 314 // The caller's behavior is controlled by the return value, which is decided 315 // by walkFn. walkFn may ignore err and return nil. 316 // If walkFn returns SkipDir, it will be handled by the caller. 317 // So walk should return whatever walkFn returns. 318 return userErr 319 } 320 for _, name := range names { 321 fileInfo, err := gw.r.Stat(ctx, name) 322 if err != nil { 323 if err := walkFn(name, fileInfo, err); err != nil && err != filepath.SkipDir { 324 return err 325 } 326 } else if err := gw.walk(ctx, name, fileInfo, walkFn); err != nil { 327 if !fileInfo.IsDir() || err != filepath.SkipDir { 328 return err 329 } 330 } 331 } 332 return nil 333 }