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  }