github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/utils/filesys/inmemfs.go (about) 1 // Copyright 2019 Dolthub, Inc. 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 filesys 16 17 import ( 18 "bytes" 19 "errors" 20 "io" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/dolthub/dolt/go/libraries/utils/iohelp" 29 "github.com/dolthub/dolt/go/libraries/utils/osutil" 30 ) 31 32 // InMemNowFunc is a func() time.Time that can be used to supply the current time. The default value gets the current 33 // time from the system clock, but it can be set to something else in order to support reproducible tests. 34 var InMemNowFunc = time.Now 35 36 type memObj interface { 37 isDir() bool 38 parent() *memDir 39 modTime() time.Time 40 } 41 42 type memFile struct { 43 absPath string 44 data []byte 45 parentDir *memDir 46 time time.Time 47 } 48 49 func (mf *memFile) isDir() bool { 50 return false 51 } 52 53 func (mf *memFile) parent() *memDir { 54 return mf.parentDir 55 } 56 57 func (mf *memFile) modTime() time.Time { 58 return mf.time 59 } 60 61 type memDir struct { 62 absPath string 63 objs map[string]memObj 64 parentDir *memDir 65 time time.Time 66 } 67 68 func newEmptyDir(path string, parent *memDir) *memDir { 69 return &memDir{path, make(map[string]memObj), parent, InMemNowFunc()} 70 } 71 72 func (md *memDir) isDir() bool { 73 return true 74 } 75 76 func (md *memDir) parent() *memDir { 77 return md.parentDir 78 } 79 80 func (md *memDir) modTime() time.Time { 81 return md.time 82 } 83 84 // InMemFS is an in memory filesystem implementation that is primarily intended for testing 85 type InMemFS struct { 86 rwLock *sync.RWMutex 87 cwd string 88 objs map[string]memObj 89 } 90 91 // EmptyInMemFS creates an empty InMemFS instance 92 func EmptyInMemFS(workingDir string) *InMemFS { 93 return NewInMemFS([]string{}, map[string][]byte{}, workingDir) 94 } 95 96 // NewInMemFS creates an InMemFS with directories and folders provided. 97 func NewInMemFS(dirs []string, files map[string][]byte, cwd string) *InMemFS { 98 if cwd == "" { 99 cwd = osutil.FileSystemRoot 100 } 101 cwd = osutil.PathToNative(cwd) 102 103 if !filepath.IsAbs(cwd) { 104 panic("cwd for InMemFilesys must be absolute path.") 105 } 106 107 fs := &InMemFS{&sync.RWMutex{}, cwd, map[string]memObj{osutil.FileSystemRoot: newEmptyDir(osutil.FileSystemRoot, nil)}} 108 109 if dirs != nil { 110 for _, dir := range dirs { 111 absDir := fs.getAbsPath(dir) 112 fs.mkDirs(absDir) 113 } 114 } 115 116 if files != nil { 117 for path, val := range files { 118 path = fs.getAbsPath(path) 119 120 dir := filepath.Dir(path) 121 targetDir, err := fs.mkDirs(dir) 122 123 if err != nil { 124 panic("Initializing InMemFS with invalid data.") 125 } 126 127 now := InMemNowFunc() 128 newFile := &memFile{path, val, targetDir, now} 129 130 targetDir.time = now 131 targetDir.objs[path] = newFile 132 fs.objs[path] = newFile 133 } 134 } 135 136 return fs 137 } 138 139 func (fs *InMemFS) getAbsPath(path string) string { 140 path = fs.pathToNative(path) 141 if strings.HasPrefix(path, osutil.FileSystemRoot) { 142 return filepath.Clean(path) 143 } 144 145 return filepath.Join(fs.cwd, path) 146 } 147 148 // Exists will tell you if a file or directory with a given path already exists, and if it does is it a directory 149 func (fs *InMemFS) Exists(path string) (exists bool, isDir bool) { 150 fs.rwLock.RLock() 151 defer fs.rwLock.RUnlock() 152 153 return fs.exists(path) 154 } 155 156 func (fs *InMemFS) exists(path string) (exists bool, isDir bool) { 157 path = fs.getAbsPath(path) 158 159 if obj, ok := fs.objs[path]; ok { 160 return true, obj.isDir() 161 } 162 163 return false, false 164 } 165 166 type iterEntry struct { 167 path string 168 size int64 169 isDir bool 170 } 171 172 func (fs *InMemFS) getIterEntries(path string, recursive bool) ([]iterEntry, error) { 173 var entries []iterEntry 174 _, err := fs.iter(fs.getAbsPath(path), recursive, func(path string, size int64, isDir bool) (stop bool) { 175 entries = append(entries, iterEntry{path, size, isDir}) 176 return false 177 }) 178 179 if err != nil { 180 return nil, err 181 } 182 183 return entries, nil 184 } 185 186 // Iter iterates over the files and subdirectories within a given directory (Optionally recursively). There 187 // are no guarantees about the ordering of results. It is also possible that concurrent delete operations could render 188 // a file path invalid when the callback is made. 189 func (fs *InMemFS) Iter(path string, recursive bool, cb FSIterCB) error { 190 entries, err := func() ([]iterEntry, error) { 191 fs.rwLock.RLock() 192 defer fs.rwLock.RUnlock() 193 194 return fs.getIterEntries(path, recursive) 195 }() 196 197 if err != nil { 198 return err 199 } 200 201 for _, entry := range entries { 202 cb(entry.path, entry.size, entry.isDir) 203 } 204 205 return nil 206 } 207 208 func (fs *InMemFS) iter(path string, recursive bool, cb FSIterCB) (bool, error) { 209 path = filepath.Clean(path) 210 obj, ok := fs.objs[path] 211 212 if !ok { 213 return true, os.ErrNotExist 214 } else if !obj.isDir() { 215 return true, ErrIsDir 216 } 217 218 dir := obj.(*memDir) 219 220 for k, v := range dir.objs { 221 var size int 222 if !v.isDir() { 223 size = len(v.(*memFile).data) 224 } 225 226 stop := cb(k, int64(size), v.isDir()) 227 228 if stop { 229 return true, nil 230 } 231 232 if v.isDir() && recursive { 233 stop, err := fs.iter(k, recursive, cb) 234 235 if stop || err != nil { 236 return stop, err 237 } 238 } 239 } 240 241 return false, nil 242 } 243 244 // OpenForRead opens a file for reading 245 func (fs *InMemFS) OpenForRead(fp string) (io.ReadCloser, error) { 246 fs.rwLock.RLock() 247 defer fs.rwLock.RUnlock() 248 249 fp = fs.getAbsPath(fp) 250 251 if exists, isDir := fs.exists(fp); !exists { 252 return nil, os.ErrNotExist 253 } else if isDir { 254 return nil, ErrIsDir 255 } 256 257 fileObj := fs.objs[fp].(*memFile) 258 buf := bytes.NewBuffer(fileObj.data) 259 260 return ioutil.NopCloser(buf), nil 261 } 262 263 // ReadFile reads the entire contents of a file 264 func (fs *InMemFS) ReadFile(fp string) ([]byte, error) { 265 fp = fs.getAbsPath(fp) 266 r, err := fs.OpenForRead(fp) 267 268 if err != nil { 269 return nil, err 270 } 271 272 return ioutil.ReadAll(r) 273 } 274 275 type inMemFSWriteCloser struct { 276 path string 277 parentDir *memDir 278 fs *InMemFS 279 buf *bytes.Buffer 280 rwLock *sync.RWMutex 281 } 282 283 func (fsw *inMemFSWriteCloser) Write(p []byte) (int, error) { 284 return fsw.buf.Write(p) 285 } 286 287 func (fsw *inMemFSWriteCloser) Close() error { 288 fsw.rwLock.Lock() 289 defer fsw.rwLock.Unlock() 290 291 now := InMemNowFunc() 292 data := fsw.buf.Bytes() 293 newFile := &memFile{fsw.path, data, fsw.parentDir, now} 294 fsw.parentDir.time = now 295 fsw.parentDir.objs[fsw.path] = newFile 296 fsw.fs.objs[fsw.path] = newFile 297 298 return nil 299 } 300 301 // OpenForWrite opens a file for writing. The file will be created if it does not exist, and if it does exist 302 // it will be overwritten. 303 func (fs *InMemFS) OpenForWrite(fp string, perm os.FileMode) (io.WriteCloser, error) { 304 fs.rwLock.Lock() 305 defer fs.rwLock.Unlock() 306 307 fp = fs.getAbsPath(fp) 308 309 if exists, isDir := fs.exists(fp); exists && isDir { 310 return nil, ErrIsDir 311 } 312 313 dir := filepath.Dir(fp) 314 parentDir, err := fs.mkDirs(dir) 315 316 if err != nil { 317 return nil, err 318 } 319 320 return &inMemFSWriteCloser{fp, parentDir, fs, bytes.NewBuffer(make([]byte, 0, 512)), fs.rwLock}, nil 321 } 322 323 // WriteFile writes the entire data buffer to a given file. The file will be created if it does not exist, 324 // and if it does exist it will be overwritten. 325 func (fs *InMemFS) WriteFile(fp string, data []byte) error { 326 w, err := fs.OpenForWrite(fp, os.ModePerm) 327 328 if err != nil { 329 return err 330 } 331 332 err = iohelp.WriteAll(w, data) 333 334 if err != nil { 335 return err 336 } 337 338 return w.Close() 339 } 340 341 // MkDirs creates a folder and all the parent folders that are necessary to create it. 342 func (fs *InMemFS) MkDirs(path string) error { 343 fs.rwLock.Lock() 344 defer fs.rwLock.Unlock() 345 346 _, err := fs.mkDirs(path) 347 return err 348 } 349 350 func (fs *InMemFS) mkDirs(path string) (*memDir, error) { 351 path = fs.getAbsPath(path) 352 elements := strings.Split(path, osutil.PathDelimiter) 353 354 currPath := osutil.FileSystemRoot 355 parentObj, ok := fs.objs[currPath] 356 357 if !ok { 358 panic("Filesystem does not have a root directory.") 359 } 360 361 parentDir := parentObj.(*memDir) 362 for i, element := range elements { 363 // When iterating Windows-style paths, the first slash is after the volume, e.g. C:/ 364 // We check if the first element (like "C:") plus the delimiter is the same as the system root 365 // If so, we skip it as we add the system root when creating the InMemFS 366 if i == 0 && osutil.IsWindows && element+osutil.PathDelimiter == osutil.FileSystemRoot { 367 continue 368 } 369 currPath = filepath.Join(currPath, element) 370 371 if obj, ok := fs.objs[currPath]; !ok { 372 newDir := newEmptyDir(currPath, parentDir) 373 parentDir.objs[currPath] = newDir 374 fs.objs[currPath] = newDir 375 parentDir = newDir 376 } else if !obj.isDir() { 377 return nil, errors.New("Could not create directory with same path as existing file: " + currPath) 378 } else { 379 parentDir = obj.(*memDir) 380 } 381 } 382 383 return parentDir, nil 384 } 385 386 // DeleteFile will delete a file at the given path 387 func (fs *InMemFS) DeleteFile(path string) error { 388 fs.rwLock.Lock() 389 defer fs.rwLock.Unlock() 390 391 return fs.deleteFile(path) 392 } 393 394 func (fs *InMemFS) deleteFile(path string) error { 395 path = fs.getAbsPath(path) 396 397 if obj, ok := fs.objs[path]; ok { 398 if obj.isDir() { 399 return ErrIsDir 400 } 401 402 delete(fs.objs, path) 403 404 parentDir := obj.parent() 405 if parentDir != nil { 406 delete(parentDir.objs, path) 407 } 408 } else { 409 return os.ErrNotExist 410 } 411 412 return nil 413 } 414 415 // Delete will delete an empty directory, or a file. If trying delete a directory that is not empty you can set force to 416 // true in order to delete the dir and all of it's contents 417 func (fs *InMemFS) Delete(path string, force bool) error { 418 fs.rwLock.Lock() 419 defer fs.rwLock.Unlock() 420 421 path = fs.getAbsPath(path) 422 423 if exists, isDir := fs.exists(path); !exists { 424 return os.ErrNotExist 425 } else if !isDir { 426 return fs.deleteFile(path) 427 } 428 429 toDelete := map[string]bool{path: true} 430 entries, err := fs.getIterEntries(path, true) 431 432 if err != nil { 433 return err 434 } 435 436 for _, entry := range entries { 437 toDelete[entry.path] = entry.isDir 438 } 439 440 isEmpty := len(toDelete) == 1 441 442 if !force && !isEmpty { 443 return errors.New(path + " is a directory which is not empty. Delete the contents first, or set force to true") 444 } 445 446 for currPath := range toDelete { 447 currObj := fs.objs[currPath] 448 delete(fs.objs, currPath) 449 450 parentDir := currObj.parent() 451 if parentDir != nil { 452 delete(parentDir.objs, currPath) 453 } 454 } 455 456 return nil 457 } 458 459 // MoveFile will move a file from the srcPath in the filesystem to the destPath 460 func (fs *InMemFS) MoveFile(srcPath, destPath string) error { 461 fs.rwLock.Lock() 462 defer fs.rwLock.Unlock() 463 464 srcPath = fs.getAbsPath(srcPath) 465 destPath = fs.getAbsPath(destPath) 466 467 if exists, destIsDir := fs.exists(destPath); exists && destIsDir { 468 return ErrIsDir 469 } 470 471 if obj, ok := fs.objs[srcPath]; ok { 472 if obj.isDir() { 473 return ErrIsDir 474 } 475 476 destDir := filepath.Dir(destPath) 477 destParentDir, err := fs.mkDirs(destDir) 478 479 if err != nil { 480 return err 481 } 482 483 now := InMemNowFunc() 484 destObj := &memFile{destPath, obj.(*memFile).data, destParentDir, now} 485 486 fs.objs[destPath] = destObj 487 delete(fs.objs, srcPath) 488 489 parentDir := obj.parent() 490 if parentDir != nil { 491 parentDir.time = now 492 delete(parentDir.objs, srcPath) 493 } 494 495 destParentDir.objs[destPath] = destObj 496 destParentDir.time = now 497 498 return nil 499 } 500 501 return os.ErrNotExist 502 } 503 504 // converts a path to an absolute path. If it's already an absolute path the input path will be returned unaltered 505 func (fs *InMemFS) Abs(path string) (string, error) { 506 path = fs.pathToNative(path) 507 if filepath.IsAbs(path) { 508 return path, nil 509 } 510 511 return filepath.Join(fs.cwd, path), nil 512 } 513 514 // LastModified gets the last modified timestamp for a file or directory at a given path 515 func (fs *InMemFS) LastModified(path string) (t time.Time, exists bool) { 516 fs.rwLock.RLock() 517 defer fs.rwLock.RUnlock() 518 519 path = fs.getAbsPath(path) 520 521 if obj, ok := fs.objs[path]; ok { 522 return obj.modTime(), true 523 } 524 525 return time.Time{}, false 526 } 527 528 func (fs *InMemFS) pathToNative(path string) string { 529 if len(path) >= 1 { 530 if path[0] == '.' { 531 if len(path) == 1 { 532 return fs.cwd 533 } 534 if len(path) >= 2 && (path[1] == '/' || path[1] == '\\') { 535 return filepath.Join(fs.cwd, path[2:]) 536 } 537 return filepath.Join(fs.cwd, path) 538 } else if !osutil.StartsWithWindowsVolume(path) && path[0] != '/' && path[0] != '\\' { 539 return filepath.Join(fs.cwd, path) 540 } 541 } 542 return osutil.PathToNative(path) 543 }