github.com/wolfi-dev/wolfictl@v0.16.11/pkg/configs/rwfs/os/memfs/memfs.go (about)

     1  package memfs
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"io/fs"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/wolfi-dev/wolfictl/pkg/configs/rwfs"
    12  )
    13  
    14  var _ rwfs.FS = (*memWriteFS)(nil)
    15  
    16  // memWriteFS is a file system that reads file content initially from the
    17  // provided fs.FS, but all writes and subsequent reads on files are based on
    18  // in-memory representations of those files, such that a memWriteFS will never
    19  // modify files on disk. This is especially useful for testing scenarios where
    20  // you have test fixture files but you want to ensure they aren't actually
    21  // modified during the test.
    22  type memWriteFS struct {
    23  	underlying fs.FS                  // The underlying file system
    24  	data       map[string]interface{} // Values can be *bytes.Buffer (for files) or memDir (for directories)
    25  	mu         sync.RWMutex
    26  }
    27  
    28  // New creates and returns a new memWriteFS based on the provided fs.FS.
    29  func New(underlying fs.FS) rwfs.FS {
    30  	return &memWriteFS{
    31  		underlying: underlying,
    32  		data:       make(map[string]interface{}),
    33  	}
    34  }
    35  
    36  func (m *memWriteFS) Open(name string) (fs.File, error) {
    37  	return m.openInternal(name, false)
    38  }
    39  
    40  func (m *memWriteFS) OpenAsWritable(name string) (rwfs.File, error) {
    41  	return m.openInternal(name, true)
    42  }
    43  
    44  func (m *memWriteFS) openInternal(name string, writable bool) (rwfs.File, error) {
    45  	m.mu.RLock()
    46  	data, exists := m.data[name]
    47  	m.mu.RUnlock()
    48  
    49  	if exists {
    50  		switch v := data.(type) {
    51  		case *bytes.Buffer:
    52  			return &memFile{
    53  				name:     name,
    54  				buf:      v,
    55  				reader:   bytes.NewReader(v.Bytes()),
    56  				writable: writable,
    57  			}, nil
    58  		case memDir:
    59  			return &memDirFile{name: name, entries: v.entries, pos: 0}, nil
    60  		default:
    61  			return nil, errors.New("unknown data type in memory")
    62  		}
    63  	}
    64  
    65  	// Open from the underlying FS
    66  	file, err := m.underlying.Open(name)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	defer file.Close()
    71  
    72  	stat, err := file.Stat()
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	if stat.IsDir() {
    78  		entries, err := fs.ReadDir(m.underlying, name)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  
    83  		// Store directory entries in memory
    84  		memDir := memDir{entries: entries}
    85  		m.mu.Lock()
    86  		m.data[name] = memDir
    87  		m.mu.Unlock()
    88  
    89  		return &memDirFile{name: name, entries: entries, pos: 0}, nil
    90  	}
    91  
    92  	buf := new(bytes.Buffer)
    93  	_, err = io.Copy(buf, file)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	// Store the file content in memory
    99  	m.mu.Lock()
   100  	m.data[name] = buf
   101  	m.mu.Unlock()
   102  
   103  	return &memFile{
   104  		name:     name,
   105  		buf:      buf,
   106  		reader:   bytes.NewReader(buf.Bytes()),
   107  		writable: writable,
   108  	}, nil
   109  }
   110  
   111  func (m *memWriteFS) Truncate(name string, size int64) error {
   112  	m.mu.Lock()
   113  	defer m.mu.Unlock()
   114  
   115  	data, exists := m.data[name]
   116  	if !exists {
   117  		return errors.New("file not found")
   118  	}
   119  
   120  	if data, ok := data.(*bytes.Buffer); ok {
   121  		data.Truncate(int(size))
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (m *memWriteFS) Create(name string) (rwfs.File, error) {
   128  	m.mu.Lock()
   129  	defer m.mu.Unlock()
   130  
   131  	// Check if the name corresponds to an existing directory
   132  	if _, ok := m.data[name].(memDir); ok {
   133  		return nil, &fs.PathError{Op: "create", Path: name, Err: errors.New("is a directory")}
   134  	}
   135  
   136  	// Create or overwrite the file in memory
   137  	buf := new(bytes.Buffer)
   138  	m.data[name] = buf
   139  
   140  	return &memFile{
   141  		name:     name,
   142  		buf:      buf,
   143  		writable: true,
   144  	}, nil
   145  }
   146  
   147  type memFile struct {
   148  	name     string
   149  	buf      *bytes.Buffer
   150  	reader   *bytes.Reader
   151  	writable bool
   152  }
   153  
   154  func (f *memFile) Read(p []byte) (n int, err error) {
   155  	return f.reader.Read(p)
   156  }
   157  
   158  func (f *memFile) Write(p []byte) (n int, err error) {
   159  	if !f.writable {
   160  		return 0, errors.New("file not opened for writing")
   161  	}
   162  	n, err = f.buf.Write(p)
   163  	f.reader = bytes.NewReader(f.buf.Bytes()) // Reset reader with updated buffer content
   164  	return n, err
   165  }
   166  
   167  func (f *memFile) Close() error {
   168  	_, err := f.reader.Seek(0, io.SeekStart)
   169  	return err
   170  }
   171  
   172  func (f *memFile) Name() string {
   173  	return f.name
   174  }
   175  
   176  func (f *memFile) Stat() (fs.FileInfo, error) {
   177  	return &memFileInfo{
   178  		name:  f.name,
   179  		size:  int64(f.buf.Len()),
   180  		isDir: false,
   181  	}, nil
   182  }
   183  
   184  func (f *memFile) Seek(offset int64, whence int) (int64, error) {
   185  	return f.reader.Seek(offset, whence)
   186  }
   187  
   188  type memFileInfo struct {
   189  	name  string
   190  	size  int64
   191  	isDir bool
   192  }
   193  
   194  func (mfi *memFileInfo) Name() string       { return mfi.name }
   195  func (mfi *memFileInfo) Size() int64        { return mfi.size }
   196  func (mfi *memFileInfo) Mode() fs.FileMode  { return 0o644 }
   197  func (mfi *memFileInfo) ModTime() time.Time { return time.Unix(0, 0) }
   198  func (mfi *memFileInfo) IsDir() bool        { return mfi.isDir }
   199  func (mfi *memFileInfo) Sys() interface{}   { return nil }
   200  
   201  type memDir struct {
   202  	entries []fs.DirEntry
   203  }
   204  
   205  type memDirFile struct {
   206  	name    string
   207  	entries []fs.DirEntry
   208  	pos     int
   209  }
   210  
   211  func (df *memDirFile) Read(_ []byte) (n int, err error) {
   212  	return 0, errors.New("cannot read from directory")
   213  }
   214  
   215  func (df *memDirFile) Write(_ []byte) (n int, err error) {
   216  	return 0, errors.New("cannot write to directory")
   217  }
   218  
   219  func (df *memDirFile) Close() error       { return nil }
   220  func (df *memDirFile) Name() string       { return df.name }
   221  func (df *memDirFile) Size() int64        { return 0 }
   222  func (df *memDirFile) Mode() fs.FileMode  { return 0o644 }
   223  func (df *memDirFile) ModTime() time.Time { return time.Unix(0, 0) }
   224  func (df *memDirFile) IsDir() bool        { return true }
   225  func (df *memDirFile) Sys() interface{}   { return nil }
   226  
   227  func (df *memDirFile) Stat() (fs.FileInfo, error) {
   228  	return &memFileInfo{
   229  		name:  df.name,
   230  		size:  0,
   231  		isDir: true,
   232  	}, nil
   233  }
   234  
   235  func (df *memDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
   236  	if n <= 0 || n > len(df.entries)-df.pos {
   237  		n = len(df.entries) - df.pos
   238  	}
   239  	entries := df.entries[df.pos : df.pos+n]
   240  	df.pos += n
   241  	return entries, nil
   242  }