Skip to content

Commit

Permalink
Simplify MemFS
Browse files Browse the repository at this point in the history
Signed-off-by: Takaya Saeki <[email protected]>
  • Loading branch information
nullpo-head committed Mar 16, 2022
1 parent e7a3250 commit e9639c5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 88 deletions.
76 changes: 44 additions & 32 deletions internal/wasi/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path"
"runtime"
"sort"
"strings"
"time"
)
Expand Down Expand Up @@ -38,29 +39,35 @@ func (m MemFS) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error
return nil, &os.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
}

dir := &memFSEntry{Entries: m}

// Path "." indicates m itself as a directory.
if name == "." {
return newMemDir(m, "."), nil
return newMemDir(dir, "."), nil
}

// Traverse directories.
files := strings.Split(name, "/")
for _, file := range files[:len(files)-1] {
var ok bool
dir, ok = dir.Entries[file]
if !ok || !dir.IsDir() {
return nil, &os.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
}

// Open or create the entry.
// Note: MemFS does not check permission for now.
entry, ok := m[name]
baseName := path.Base(name)
entry, ok := dir.Entries[baseName]
if !ok {
if flag&os.O_CREATE == 0 {
return nil, &os.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}

// If the directory of the new path is not root, the directory must exist.
dirName := path.Dir(name)
dir, ok := m[dirName]
if dirName != "." && (!ok || !dir.Mode.IsDir()) {
return nil, &os.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}

// Create a new file entry.
entry = &memFSEntry{Mode: perm}
m[name] = entry
// Create a new entry.
entry = &memFSEntry{}
dir.Entries[baseName] = entry
} else if flag&os.O_EXCL != 0 && flag&os.O_CREATE != 0 {
// when O_EXCL is set with O_CREATE, the file must not already exist.
return nil, &os.PathError{Op: "open", Path: name, Err: fs.ErrExist}
Expand All @@ -70,10 +77,10 @@ func (m MemFS) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error
entry.Contents = []byte{}
}

if entry.Mode.IsDir() {
return newMemDir(m, name), nil
if entry.IsDir() {
return newMemDir(entry, baseName), nil
} else {
return &memFile{name: path.Base(name), fsEntry: entry}, nil
return &memFile{name: baseName, fsEntry: entry}, nil
}
}

Expand All @@ -84,12 +91,15 @@ func (m *MemFS) Open(path string) (fs.File, error) {

// memFSEntry represents a file or directory entry of memFS.
type memFSEntry struct {
// the contents of the file if this file is a regular file.
// Contents of the file if this file is a regular file.
Contents []byte
// The file Mode. Note that memFS does not check Mode except ModeDir.
Mode fs.FileMode
// Entries if this file is a directory. Otherwise, nil.
Entries map[string]*memFSEntry
}

// IsDir returns if this entry is a directory.
func (e *memFSEntry) IsDir() bool { return e.Entries != nil }

// memFile represents an opened memFS regular file.
// memFile implements fs.File, io.Writer, and io.Seeker.
type memFile struct {
Expand Down Expand Up @@ -158,24 +168,20 @@ type memDir struct {
fsEntry *memFSEntry
}

func newMemDir(m MemFS, dirName string) *memDir {
// Collect the file list of the directory.
func newMemDir(dir *memFSEntry, baseName string) *memDir {
// Cache the file list of the directory for ReadDir to return the result in the consistent order.
// Note that it's ok to return stale file list if the directory is modified after Open.
// The result of ReadDir is undefined in POSIX in that situation, so WASI will be the same.
entries := make([]fs.DirEntry, 0)
for name, entry := range m {
if name == dirName {
continue
}
if path.Dir(name) == dirName {
entries = append(entries, &memFileInfo{name: path.Base(name), fsEntry: entry})
}
for name, entry := range dir.Entries {
entries = append(entries, &memFileInfo{name: path.Base(name), fsEntry: entry})
}
sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() })

return &memDir{
name: path.Base(dirName),
name: baseName,
entries: entries,
fsEntry: m[dirName],
fsEntry: dir,
}
}

Expand Down Expand Up @@ -226,7 +232,7 @@ func (d *memDir) ReadDir(n int) ([]fs.DirEntry, error) {
return entries, nil
}

// memFileInfo represents a FileInfo of an opened memFile and memDir.
// memFileInfo represents a FileInfo of an opened memFile or memDir.
// memFileInfo implements fs.FileInfo and fs.DirEntry
type memFileInfo struct {
// name of this file, not a full path but a base name.
Expand All @@ -242,7 +248,13 @@ func (e *memFileInfo) Name() string { return e.name }
func (e *memFileInfo) Size() int64 { return int64(len(e.fsEntry.Contents)) }

// Mode implements fs.FileInfo.Mode
func (e *memFileInfo) Mode() fs.FileMode { return e.fsEntry.Mode }
func (e *memFileInfo) Mode() fs.FileMode {
mode := fs.FileMode(0777)
if e.fsEntry.IsDir() {
mode |= fs.ModeDir
}
return mode
}

// Type implements fs.FileInfo.Type
func (e *memFileInfo) Type() fs.FileMode { return e.Mode().Type() }
Expand All @@ -251,7 +263,7 @@ func (e *memFileInfo) Type() fs.FileMode { return e.Mode().Type() }
func (e *memFileInfo) ModTime() time.Time { return time.Time{} } // return the empty value for now

// IsDir implements fs.FileInfo.IsDir
func (f *memFileInfo) IsDir() bool { return f.fsEntry.Mode.IsDir() }
func (f *memFileInfo) IsDir() bool { return f.Mode().IsDir() }

// Sys implements fs.FileInfo.Sys
func (f *memFileInfo) Sys() interface{} { return nil }
Expand Down
88 changes: 36 additions & 52 deletions internal/wasi/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,31 @@ func TestMemFile_Read_Seek(t *testing.T) {
// See fstest.TestFS
func TestMemFS_FSInterface(t *testing.T) {
memFS := &MemFS{
"simple file": {Contents: []byte("simple cont")},
"directory with children": {Mode: fs.ModeDir},
"directory with children/file1 inside directory": {Contents: []byte("simple cont file1 inside directory")},
"directory with children/file2 inside directory": {Contents: []byte("simple cont file2 inside directory")},
"directory with children/nested directory": {Mode: fs.ModeDir},
"directory with children/nested directory/file1 inside directory": {Contents: []byte("simple cont file1 inside directory")},
"directory with children/nested directory with no children": {Mode: fs.ModeDir},
"directory with no children": {Mode: fs.ModeDir},
"simple file": {Contents: []byte("simple cont")},
"directory": {
Entries: map[string]*memFSEntry{
"file1 inside directory": {Contents: []byte("simple cont file1 inside directory")},
"file2 inside directory": {Contents: []byte("simple cont file2 inside directory")},
"nested directory": {
Entries: map[string]*memFSEntry{
"file1 inside directory": {Contents: []byte("simple cont file1 inside directory")},
},
},
"nested directory with no children": {Entries: map[string]*memFSEntry{}},
},
},
"directory with no children": {Entries: map[string]*memFSEntry{}},
}

// TestFS tests that fs.FS correctly does the expected operations.
err := fstest.TestFS(memFS,
"simple file",
"directory with children",
"directory with children/file1 inside directory",
"directory with children/file2 inside directory",
"directory with children/nested directory",
"directory with children/nested directory/file1 inside directory",
"directory with children/nested directory with no children",
"directory",
"directory/file1 inside directory",
"directory/file2 inside directory",
"directory/nested directory",
"directory/nested directory/file1 inside directory",
"directory/nested directory with no children",
"directory with no children")
require.NoError(t, err)
}
Expand All @@ -62,61 +68,48 @@ func TestMemFS_OpenFile_Flags(t *testing.T) {
testName string
fileName string
flag int
perm os.FileMode
expectedResult *memFile
expectedFS func(openFileResult *memFile) *MemFS
}{
{
testName: "new file",
fileName: "new file", // arbitrary new file name
flag: os.O_CREATE,
perm: os.FileMode(0777), // arbitrary perm
expectedResult: &memFile{
name: "new file",
fsEntry: &memFSEntry{
Mode: os.FileMode(0777),
},
name: "new file",
fsEntry: &memFSEntry{},
},
expectedFS: func(expectedResult *memFile) *MemFS {
return &MemFS{
"new file": expectedResult.fsEntry,
"file": {
Contents: []byte("simple cont"),
},
"dir": {
Mode: fs.ModeDir,
},
"file": {Contents: []byte("simple cont")},
"dir": {Entries: map[string]*memFSEntry{}},
}
},
},
{
testName: "new file inside a directory",
fileName: "dir/new file", // arbitrary new file name
flag: os.O_CREATE,
perm: os.FileMode(0777), // arbitrary perm
expectedResult: &memFile{
name: "new file",
fsEntry: &memFSEntry{
Mode: os.FileMode(0777),
},
name: "new file",
fsEntry: &memFSEntry{},
},
expectedFS: func(expectedResult *memFile) *MemFS {
return &MemFS{
"file": {
Contents: []byte("simple cont"),
},
"file": {Contents: []byte("simple cont")},
"dir": {
Mode: fs.ModeDir,
Entries: map[string]*memFSEntry{
"new file": expectedResult.fsEntry,
},
},
"dir/new file": expectedResult.fsEntry,
}
},
},
{
testName: "O_CREATE should have no effect on existing file",
fileName: "file", // existing file
flag: os.O_CREATE,
perm: os.FileMode(0777), // arbitrary perm, should have no effect
expectedResult: &memFile{
name: "file",
fsEntry: &memFSEntry{
Expand All @@ -126,17 +119,14 @@ func TestMemFS_OpenFile_Flags(t *testing.T) {
expectedFS: func(expectedResult *memFile) *MemFS {
return &MemFS{
"file": expectedResult.fsEntry,
"dir": {
Mode: fs.ModeDir,
},
"dir": {Entries: map[string]*memFSEntry{}},
}
},
},
{
testName: "O_TRUNC should trunc the contents",
fileName: "file", // existing file
flag: os.O_TRUNC,
perm: os.FileMode(0777), // arbitrary perm, should have no effect
expectedResult: &memFile{
name: "file",
fsEntry: &memFSEntry{
Expand All @@ -146,9 +136,7 @@ func TestMemFS_OpenFile_Flags(t *testing.T) {
expectedFS: func(expectedResult *memFile) *MemFS {
return &MemFS{
"file": expectedResult.fsEntry,
"dir": {
Mode: fs.ModeDir,
},
"dir": {Entries: map[string]*memFSEntry{}},
}
},
},
Expand All @@ -158,15 +146,11 @@ func TestMemFS_OpenFile_Flags(t *testing.T) {
tc := tt
t.Run(tc.testName, func(t *testing.T) {
memFS := &MemFS{
"file": {
Contents: []byte("simple cont"),
},
"dir": {
Mode: fs.ModeDir,
},
"file": {Contents: []byte("simple cont")},
"dir": {Entries: map[string]*memFSEntry{}},
}

file, err := memFS.OpenFile(tc.fileName, tc.flag, tc.perm)
file, err := memFS.OpenFile(tc.fileName, tc.flag, 0)
require.NoError(t, err)
require.Equal(t, tc.expectedResult, file.(*memFile))
require.Equal(t, tc.expectedFS(tc.expectedResult), memFS)
Expand All @@ -181,7 +165,7 @@ func TestMemFS_OpenFile_Flags_Errors(t *testing.T) {
Contents: []byte("simple cont"),
},
"dir": {
Mode: fs.ModeDir,
Entries: map[string]*memFSEntry{},
},
}

Expand Down
10 changes: 6 additions & 4 deletions internal/wasi/wasi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"math/rand"
"os"
"testing"
Expand Down Expand Up @@ -1654,9 +1653,12 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {

// MemFS for testing
memFS := &MemFS{
"wazero": {},
"dir": {Mode: fs.ModeDir},
"dir/wazero": {},
"wazero": {},
"dir": {
Entries: map[string]*memFSEntry{
"wazero": {},
},
},
}

ctx := context.Background()
Expand Down

0 comments on commit e9639c5

Please sign in to comment.