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 }