github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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 "encoding/base32" 20 "errors" 21 "fmt" 22 "io" 23 "math/rand" 24 "os" 25 "path/filepath" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/dolthub/dolt/go/libraries/utils/iohelp" 31 "github.com/dolthub/dolt/go/libraries/utils/lockutil" 32 "github.com/dolthub/dolt/go/libraries/utils/osutil" 33 ) 34 35 // InMemNowFunc is a func() time.Time that can be used to supply the current time. The default value gets the current 36 // time from the system clock, but it can be set to something else in order to support reproducible tests. 37 var InMemNowFunc = time.Now 38 39 type memObj interface { 40 isDir() bool 41 parent() *memDir 42 modTime() time.Time 43 } 44 45 type memFile struct { 46 absPath string 47 data []byte 48 parentDir *memDir 49 time time.Time 50 } 51 52 func (mf *memFile) isDir() bool { 53 return false 54 } 55 56 func (mf *memFile) parent() *memDir { 57 return mf.parentDir 58 } 59 60 func (mf *memFile) modTime() time.Time { 61 return mf.time 62 } 63 64 type memDir struct { 65 absPath string 66 objs map[string]memObj 67 parentDir *memDir 68 time time.Time 69 } 70 71 func newEmptyDir(path string, parent *memDir) *memDir { 72 return &memDir{path, make(map[string]memObj), parent, InMemNowFunc()} 73 } 74 75 func (md *memDir) isDir() bool { 76 return true 77 } 78 79 func (md *memDir) parent() *memDir { 80 return md.parentDir 81 } 82 83 func (md *memDir) modTime() time.Time { 84 return md.time 85 } 86 87 // InMemFS is an in memory filesystem implementation that is primarily intended for testing 88 type InMemFS struct { 89 rwLock *sync.RWMutex 90 cwd string 91 objs map[string]memObj 92 } 93 94 var _ Filesys = (*InMemFS)(nil) 95 96 // EmptyInMemFS creates an empty InMemFS instance 97 func EmptyInMemFS(workingDir string) *InMemFS { 98 return NewInMemFS([]string{}, map[string][]byte{}, workingDir) 99 } 100 101 // NewInMemFS creates an InMemFS with directories and folders provided. 102 func NewInMemFS(dirs []string, files map[string][]byte, cwd string) *InMemFS { 103 if cwd == "" { 104 cwd = osutil.FileSystemRoot 105 } 106 cwd = osutil.PathToNative(cwd) 107 108 if !filepath.IsAbs(cwd) { 109 panic("cwd for InMemFilesys must be absolute path.") 110 } 111 112 fs := &InMemFS{&sync.RWMutex{}, cwd, map[string]memObj{osutil.FileSystemRoot: newEmptyDir(osutil.FileSystemRoot, nil)}} 113 114 if dirs != nil { 115 for _, dir := range dirs { 116 absDir := fs.getAbsPath(dir) 117 fs.mkDirs(absDir) 118 } 119 } 120 121 if files != nil { 122 for path, val := range files { 123 path = fs.getAbsPath(path) 124 125 dir := filepath.Dir(path) 126 targetDir, err := fs.mkDirs(dir) 127 128 if err != nil { 129 panic("Initializing InMemFS with invalid data.") 130 } 131 132 now := InMemNowFunc() 133 newFile := &memFile{path, val, targetDir, now} 134 135 targetDir.time = now 136 targetDir.objs[path] = newFile 137 fs.objs[path] = newFile 138 } 139 } 140 141 return fs 142 } 143 144 // WithWorkingDir returns a copy of this file system with the current working dir set to the path given 145 func (fs InMemFS) WithWorkingDir(path string) (Filesys, error) { 146 abs, err := fs.Abs(path) 147 if err != nil { 148 return nil, err 149 } 150 151 fs.cwd = abs 152 return &fs, nil 153 } 154 155 func (fs *InMemFS) getAbsPath(path string) string { 156 path = fs.pathToNative(path) 157 if strings.HasPrefix(path, osutil.FileSystemRoot) { 158 return filepath.Clean(path) 159 } 160 161 return filepath.Join(fs.cwd, path) 162 } 163 164 // Exists will tell you if a file or directory with a given path already exists, and if it does is it a directory 165 func (fs *InMemFS) Exists(path string) (exists bool, isDir bool) { 166 fs.rwLock.RLock() 167 defer fs.rwLock.RUnlock() 168 169 return fs.exists(path) 170 } 171 172 func (fs *InMemFS) exists(path string) (exists bool, isDir bool) { 173 path = fs.getAbsPath(path) 174 175 if obj, ok := fs.objs[path]; ok { 176 return true, obj.isDir() 177 } 178 179 return false, false 180 } 181 182 type iterEntry struct { 183 path string 184 size int64 185 isDir bool 186 } 187 188 func (fs *InMemFS) getIterEntries(path string, recursive bool) ([]iterEntry, error) { 189 var entries []iterEntry 190 _, err := fs.iter(fs.getAbsPath(path), recursive, func(path string, size int64, isDir bool) (stop bool) { 191 entries = append(entries, iterEntry{path, size, isDir}) 192 return false 193 }) 194 195 if err != nil { 196 return nil, err 197 } 198 199 return entries, nil 200 } 201 202 // Iter iterates over the files and subdirectories within a given directory (Optionally recursively). There 203 // are no guarantees about the ordering of results. It is also possible that concurrent delete operations could render 204 // a file path invalid when the callback is made. 205 func (fs *InMemFS) Iter(path string, recursive bool, cb FSIterCB) error { 206 entries, err := func() ([]iterEntry, error) { 207 fs.rwLock.RLock() 208 defer fs.rwLock.RUnlock() 209 210 return fs.getIterEntries(path, recursive) 211 }() 212 213 if err != nil { 214 return err 215 } 216 217 for _, entry := range entries { 218 cb(entry.path, entry.size, entry.isDir) 219 } 220 221 return nil 222 } 223 224 func (fs *InMemFS) iter(path string, recursive bool, cb FSIterCB) (bool, error) { 225 path = filepath.Clean(path) 226 obj, ok := fs.objs[path] 227 228 if !ok { 229 return true, os.ErrNotExist 230 } else if !obj.isDir() { 231 return true, ErrIsDir 232 } 233 234 dir := obj.(*memDir) 235 236 for k, v := range dir.objs { 237 var size int 238 if !v.isDir() { 239 size = len(v.(*memFile).data) 240 } 241 242 stop := cb(k, int64(size), v.isDir()) 243 244 if stop { 245 return true, nil 246 } 247 248 if v.isDir() && recursive { 249 stop, err := fs.iter(k, recursive, cb) 250 251 if stop || err != nil { 252 return stop, err 253 } 254 } 255 } 256 257 return false, nil 258 } 259 260 // OpenForRead opens a file for reading 261 func (fs *InMemFS) OpenForRead(fp string) (io.ReadCloser, error) { 262 fs.rwLock.RLock() 263 defer fs.rwLock.RUnlock() 264 265 fp = fs.getAbsPath(fp) 266 267 if exists, isDir := fs.exists(fp); !exists { 268 return nil, os.ErrNotExist 269 } else if isDir { 270 return nil, ErrIsDir 271 } 272 273 fileObj := fs.objs[fp].(*memFile) 274 buf := bytes.NewReader(fileObj.data) 275 276 return io.NopCloser(buf), nil 277 } 278 279 // ReadFile reads the entire contents of a file 280 func (fs *InMemFS) ReadFile(fp string) ([]byte, error) { 281 fp = fs.getAbsPath(fp) 282 r, err := fs.OpenForRead(fp) 283 284 if err != nil { 285 return nil, err 286 } 287 288 return io.ReadAll(r) 289 } 290 291 type inMemFSWriteCloser struct { 292 path string 293 parentDir *memDir 294 fs *InMemFS 295 buf *bytes.Buffer 296 rwLock *sync.RWMutex 297 } 298 299 func (fsw *inMemFSWriteCloser) Write(p []byte) (int, error) { 300 return fsw.buf.Write(p) 301 } 302 303 func (fsw *inMemFSWriteCloser) Close() error { 304 fsw.rwLock.Lock() 305 defer fsw.rwLock.Unlock() 306 307 now := InMemNowFunc() 308 data := fsw.buf.Bytes() 309 newFile := &memFile{fsw.path, data, fsw.parentDir, now} 310 fsw.parentDir.time = now 311 fsw.parentDir.objs[fsw.path] = newFile 312 fsw.fs.objs[fsw.path] = newFile 313 314 return nil 315 } 316 317 // OpenForWrite opens a file for writing. The file will be created if it does not exist, and if it does exist 318 // it will be overwritten. 319 func (fs *InMemFS) OpenForWrite(fp string, perm os.FileMode) (io.WriteCloser, error) { 320 fs.rwLock.Lock() 321 defer fs.rwLock.Unlock() 322 323 fp = fs.getAbsPath(fp) 324 325 if exists, isDir := fs.exists(fp); exists && isDir { 326 return nil, ErrIsDir 327 } 328 329 dir := filepath.Dir(fp) 330 parentDir, err := fs.mkDirs(dir) 331 332 if err != nil { 333 return nil, err 334 } 335 336 return &inMemFSWriteCloser{fp, parentDir, fs, bytes.NewBuffer(make([]byte, 0, 512)), fs.rwLock}, nil 337 } 338 339 // OpenForWriteAppend opens a file for writing. The file will be created if it does not exist, and if it does exist 340 // it will append to existing file. 341 func (fs *InMemFS) OpenForWriteAppend(fp string, perm os.FileMode) (io.WriteCloser, error) { 342 fs.rwLock.Lock() 343 defer fs.rwLock.Unlock() 344 345 fp = fs.getAbsPath(fp) 346 347 if exists, isDir := fs.exists(fp); exists && isDir { 348 return nil, ErrIsDir 349 } 350 351 dir := filepath.Dir(fp) 352 parentDir, err := fs.mkDirs(dir) 353 354 if err != nil { 355 return nil, err 356 } 357 358 return &inMemFSWriteCloser{fp, parentDir, fs, bytes.NewBuffer(make([]byte, 0, 512)), fs.rwLock}, nil 359 } 360 361 // WriteFile writes the entire data buffer to a given file. The file will be created if it does not exist, 362 // and if it does exist it will be overwritten. 363 func (fs *InMemFS) WriteFile(fp string, data []byte, perm os.FileMode) error { 364 w, err := fs.OpenForWrite(fp, perm) 365 366 if err != nil { 367 return err 368 } 369 370 err = iohelp.WriteAll(w, data) 371 372 if err != nil { 373 return err 374 } 375 376 return w.Close() 377 } 378 379 // MkDirs creates a folder and all the parent folders that are necessary to create it. 380 func (fs *InMemFS) MkDirs(path string) error { 381 fs.rwLock.Lock() 382 defer fs.rwLock.Unlock() 383 384 _, err := fs.mkDirs(path) 385 return err 386 } 387 388 func (fs *InMemFS) mkDirs(path string) (*memDir, error) { 389 path = fs.getAbsPath(path) 390 elements := strings.Split(path, osutil.PathDelimiter) 391 392 currPath := osutil.FileSystemRoot 393 parentObj, ok := fs.objs[currPath] 394 395 if !ok { 396 panic("Filesystem does not have a root directory.") 397 } 398 399 parentDir := parentObj.(*memDir) 400 for i, element := range elements { 401 // When iterating Windows-style paths, the first slash is after the volume, e.g. C:/ 402 // We check if the first element (like "C:") plus the delimiter is the same as the system root 403 // If so, we skip it as we add the system root when creating the InMemFS 404 if i == 0 && osutil.IsWindows && element+osutil.PathDelimiter == osutil.FileSystemRoot { 405 continue 406 } 407 currPath = filepath.Join(currPath, element) 408 409 if obj, ok := fs.objs[currPath]; !ok { 410 newDir := newEmptyDir(currPath, parentDir) 411 parentDir.objs[currPath] = newDir 412 fs.objs[currPath] = newDir 413 parentDir = newDir 414 } else if !obj.isDir() { 415 return nil, errors.New("Could not create directory with same path as existing file: " + currPath) 416 } else { 417 parentDir = obj.(*memDir) 418 } 419 } 420 421 return parentDir, nil 422 } 423 424 // DeleteFile will delete a file at the given path 425 func (fs *InMemFS) DeleteFile(path string) error { 426 fs.rwLock.Lock() 427 defer fs.rwLock.Unlock() 428 429 return fs.deleteFile(path) 430 } 431 432 func (fs *InMemFS) deleteFile(path string) error { 433 path = fs.getAbsPath(path) 434 435 if obj, ok := fs.objs[path]; ok { 436 if obj.isDir() { 437 return ErrIsDir 438 } 439 440 delete(fs.objs, path) 441 442 parentDir := obj.parent() 443 if parentDir != nil { 444 delete(parentDir.objs, path) 445 } 446 } else { 447 return os.ErrNotExist 448 } 449 450 return nil 451 } 452 453 // Delete will delete an empty directory, or a file. If trying delete a directory that is not empty you can set force to 454 // true in order to delete the dir and all of it's contents 455 func (fs *InMemFS) Delete(path string, force bool) error { 456 fs.rwLock.Lock() 457 defer fs.rwLock.Unlock() 458 459 path = fs.getAbsPath(path) 460 461 if exists, isDir := fs.exists(path); !exists { 462 return os.ErrNotExist 463 } else if !isDir { 464 return fs.deleteFile(path) 465 } 466 467 toDelete := map[string]bool{path: true} 468 entries, err := fs.getIterEntries(path, true) 469 470 if err != nil { 471 return err 472 } 473 474 for _, entry := range entries { 475 toDelete[entry.path] = entry.isDir 476 } 477 478 isEmpty := len(toDelete) == 1 479 480 if !force && !isEmpty { 481 return errors.New(path + " is a directory which is not empty. Delete the contents first, or set force to true") 482 } 483 484 for currPath := range toDelete { 485 currObj := fs.objs[currPath] 486 delete(fs.objs, currPath) 487 488 parentDir := currObj.parent() 489 if parentDir != nil { 490 delete(parentDir.objs, currPath) 491 } 492 } 493 494 return nil 495 } 496 497 func (fs *InMemFS) MoveDir(srcPath, destPath string) error { 498 fs.rwLock.Lock() 499 defer fs.rwLock.Unlock() 500 501 srcPath = fs.getAbsPath(srcPath) 502 destPath = fs.getAbsPath(destPath) 503 504 destPathParent, _ := filepath.Split(destPath) 505 if exists, destIsDir := fs.exists(destPathParent); !exists || !destIsDir { 506 return ErrDirNotExist 507 } 508 509 obj, ok := fs.objs[srcPath] 510 if !ok { 511 return os.ErrNotExist 512 } 513 514 if !obj.isDir() { 515 return ErrIsFile 516 } 517 518 return fs.moveDirHelper(obj.(*memDir), destPath) 519 } 520 521 func (fs *InMemFS) moveDirHelper(dir *memDir, destPath string) error { 522 // All calls to moveDirHelper MUST happen with the filesystem's read-write mutex locked 523 if err := lockutil.AssertRWMutexIsLocked(fs.rwLock); err != nil { 524 return fmt.Errorf("moveDirHelper called without first aquiring filesystem read-write lock") 525 } 526 527 if _, exists := fs.objs[destPath]; exists { 528 return fmt.Errorf("destination path exists: %s", destPath) 529 } 530 531 if _, exists := fs.objs[filepath.Dir(destPath)]; !exists { 532 return fmt.Errorf("destination parent dir does NOT exist: %s", filepath.Dir(destPath)) 533 } 534 535 // Create the base directory in the new location before we process the files in dir 536 parentDir := filepath.Dir(destPath) 537 destParentDir := fs.objs[parentDir].(*memDir) 538 destObj := &memDir{ 539 absPath: destPath, 540 objs: make(map[string]memObj), 541 parentDir: destParentDir, 542 time: InMemNowFunc(), 543 } 544 fs.objs[destPath] = destObj 545 destParentDir.objs[destPath] = destObj 546 destParentDir.time = InMemNowFunc() 547 548 for _, v := range dir.objs { 549 switch obj := v.(type) { 550 case *memDir: 551 base := filepath.Base(obj.absPath) 552 newPath := filepath.Join(destPath, base) 553 if err := fs.moveDirHelper(obj, newPath); err != nil { 554 return err 555 } 556 case *memFile: 557 base := filepath.Base(obj.absPath) 558 newDestPath := filepath.Join(destPath, base) 559 if err := fs.moveFileHelper(obj, newDestPath); err != nil { 560 return err 561 } 562 delete(dir.objs, obj.absPath) 563 delete(fs.objs, obj.absPath) 564 default: 565 return fmt.Errorf("unexpected type of memory object: %T", v) 566 } 567 } 568 569 delete(dir.parentDir.objs, dir.absPath) 570 delete(fs.objs, dir.absPath) 571 return nil 572 } 573 574 // MoveFile will move a file from the srcPath in the filesystem to the destPath 575 func (fs *InMemFS) MoveFile(srcPath, destPath string) error { 576 fs.rwLock.Lock() 577 defer fs.rwLock.Unlock() 578 579 srcPath = fs.getAbsPath(srcPath) 580 destPath = fs.getAbsPath(destPath) 581 582 if exists, destIsDir := fs.exists(destPath); exists && destIsDir { 583 return ErrIsDir 584 } 585 586 if obj, ok := fs.objs[srcPath]; ok { 587 if obj.isDir() { 588 return ErrIsDir 589 } 590 591 return fs.moveFileHelper(obj.(*memFile), destPath) 592 } 593 594 return os.ErrNotExist 595 } 596 597 func (fs *InMemFS) moveFileHelper(obj *memFile, destPath string) error { 598 // All calls to moveFileHelper MUST happen with the filesystem's read-write mutex locked 599 if err := lockutil.AssertRWMutexIsLocked(fs.rwLock); err != nil { 600 return fmt.Errorf("moveFileHelper called without first aquiring filesystem read-write lock") 601 } 602 603 destDir := filepath.Dir(destPath) 604 destParentDir, err := fs.mkDirs(destDir) 605 if err != nil { 606 return err 607 } 608 609 now := InMemNowFunc() 610 destObj := &memFile{destPath, obj.data, destParentDir, now} 611 612 fs.objs[destPath] = destObj 613 614 delete(fs.objs, obj.absPath) 615 616 parentDir := obj.parent() 617 if parentDir != nil { 618 parentDir.time = now 619 delete(parentDir.objs, obj.absPath) 620 } 621 622 destParentDir.objs[destPath] = destObj 623 destParentDir.time = now 624 625 return nil 626 } 627 628 func (fs *InMemFS) CopyFile(srcPath, destPath string) error { 629 fs.rwLock.Lock() 630 defer fs.rwLock.Unlock() 631 632 srcPath = fs.getAbsPath(srcPath) 633 destPath = fs.getAbsPath(destPath) 634 635 if exists, destIsDir := fs.exists(destPath); exists && destIsDir { 636 return ErrIsDir 637 } 638 639 if obj, ok := fs.objs[srcPath]; ok { 640 if obj.isDir() { 641 return ErrIsDir 642 } 643 644 destDir := filepath.Dir(destPath) 645 destParentDir, err := fs.mkDirs(destDir) 646 if err != nil { 647 return err 648 } 649 650 destData := make([]byte, len(obj.(*memFile).data)) 651 copy(destData, obj.(*memFile).data) 652 653 now := InMemNowFunc() 654 destObj := &memFile{destPath, destData, destParentDir, now} 655 656 fs.objs[destPath] = destObj 657 destParentDir.objs[destPath] = destObj 658 destParentDir.time = now 659 660 return nil 661 } 662 663 return os.ErrNotExist 664 } 665 666 // converts a path to an absolute path. If it's already an absolute path the input path will be returned unaltered 667 func (fs *InMemFS) Abs(path string) (string, error) { 668 path = fs.pathToNative(path) 669 if filepath.IsAbs(path) { 670 return path, nil 671 } 672 673 return filepath.Join(fs.cwd, path), nil 674 } 675 676 // LastModified gets the last modified timestamp for a file or directory at a given path 677 func (fs *InMemFS) LastModified(path string) (t time.Time, exists bool) { 678 fs.rwLock.RLock() 679 defer fs.rwLock.RUnlock() 680 681 path = fs.getAbsPath(path) 682 683 if obj, ok := fs.objs[path]; ok { 684 return obj.modTime(), true 685 } 686 687 return time.Time{}, false 688 } 689 690 func (fs *InMemFS) TempDir() string { 691 buf := make([]byte, 16) 692 rand.Read(buf) 693 s := base32.HexEncoding.EncodeToString(buf) 694 return "/var/folders/gc/" + s + "/T/" 695 } 696 697 func (fs *InMemFS) pathToNative(path string) string { 698 if len(path) >= 1 { 699 if path[0] == '.' { 700 if len(path) == 1 { 701 return fs.cwd 702 } 703 if len(path) >= 2 && (path[1] == '/' || path[1] == '\\') { 704 return filepath.Join(fs.cwd, path[2:]) 705 } 706 return filepath.Join(fs.cwd, path) 707 } else if !osutil.StartsWithWindowsVolume(path) && path[0] != '/' && path[0] != '\\' { 708 return filepath.Join(fs.cwd, path) 709 } 710 } 711 return osutil.PathToNative(path) 712 }