Skip to content

Commit

Permalink
simulation: add mmap and other syscalls for bolt
Browse files Browse the repository at this point in the history
Support running go.etcd.io/bbolt. The biggest requirement is mmap which
the simulation implements naively with a big slice that is updated on
every write to the underlying file.
  • Loading branch information
jellevandenhooff committed Nov 22, 2024
1 parent 276c7f5 commit 057c053
Show file tree
Hide file tree
Showing 16 changed files with 744 additions and 10 deletions.
4 changes: 2 additions & 2 deletions internal/hooks/go123/golangorg_x_sys_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ func GolangOrgXSysUnix_RawSyscallNoError(trap, a1, a2, a3 uintptr) (r1, r2 uintp
}

func GolangOrgXSysUnix_Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
panic("gosim not implemented")
return simulation.RawSyscall6(trap, a1, a2, a3, 0, 0, 0)
}

func GolangOrgXSysUnix_Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) {
return 0, 0, syscall.ENOSYS
return simulation.RawSyscall6(trap, a1, a2, a3, a4, a5, a6)
}

func GolangOrgXSysUnix_RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
Expand Down
20 changes: 20 additions & 0 deletions internal/hooks/go123/golangorg_x_sys_unix_gensyscall_amd64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions internal/hooks/go123/golangorg_x_sys_unix_gensyscall_arm64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions internal/hooks/go123/syscall_gensyscall_amd64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions internal/hooks/go123/syscall_gensyscall_arm64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 73 additions & 1 deletion internal/simulation/fs/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ import (
"slices"
"sync"
"syscall"
"unsafe"

"github.com/jellevandenhooff/gosim/internal/simulation/syscallabi"
)

// A Mmap tracks an open mmap for a given file. Mmaps are implemented by keeping
// a slice around with the contents of the file.
//
// Only read-only mmaps are supported since we do not get notified on writes.
// That is enough to make go.etcd.io/bbolt work.
type Mmap struct {
Inode int
Data syscallabi.ByteSliceView
}

// next:
// - delete on test end, somehow (but pool isn't shared between tests? maybe we could/should make it?)
// - think about where we should invoke GC and why that's safe
Expand All @@ -27,6 +38,10 @@ type backingFile struct {
file *chunkedFile

linkCount int

mmaps []*Mmap // open mmaps for this file
// TODO: this is used both for the persisted and in-memory
// file but it does not make sense for persisted files?
}

var zeroes = make([]byte, 1024)
Expand All @@ -45,11 +60,33 @@ func (f *backingFile) len() int {
}

func (f *backingFile) resize(size int) {
oldSize := f.file.size
f.file.Resize(size)

// update mmaps
for _, mmap := range f.mmaps {
if size < oldSize && size <= mmap.Data.Len() {
// Shrink and zero bytes that have been truncated.
// When growing the file this is not necessary as
// the default is zeroes.
tail := mmap.Data.Slice(size, min(mmap.Data.Len(), oldSize))
for tail.Len() > 0 {
n := tail.Write(zeroes)
tail = tail.SliceFrom(n)
}
}
}
}

func (f *backingFile) write(pos int, size int, chunks []*refCountedChunk) {
f.file.Write(pos, size, chunks)

// update mmaps
for _, mmap := range f.mmaps {
if pos < mmap.Data.Len() {
f.read(int64(pos), mmap.Data.Slice(pos, min(pos+size, mmap.Data.Len())))
}
}
}

func (f *backingFile) clone() *backingFile {
Expand Down Expand Up @@ -631,13 +668,14 @@ func (fs *Filesystem) Mkdir(name string) error {
type StatResp struct {
Inode int
IsDir bool
Size int64
}

// XXX: these two stats should share implementation

func (fs *Filesystem) statLocked(inode int) (StatResp, error) {
obj := fs.mem.objects[inode]
switch obj.(type) {
switch obj := obj.(type) {
case *backingDir:
return StatResp{
Inode: inode,
Expand All @@ -648,6 +686,7 @@ func (fs *Filesystem) statLocked(inode int) (StatResp, error) {
return StatResp{
Inode: inode,
IsDir: false,
Size: int64(obj.file.size),
}, nil

default:
Expand Down Expand Up @@ -849,3 +888,36 @@ func (fs *Filesystem) GetInodeInfo(inode int) InodeInfo {
defer fs.mu.Unlock()
return fs.getInodeInfoLocked(inode)
}

// Open a mmap for the given inode. Mmap starts at offset 0.
func (fs *Filesystem) Mmap(inode int, len int) *Mmap {
fs.mu.Lock()
defer fs.mu.Unlock()

backingArray := make([]byte, len)
byteView := syscallabi.NewSliceView(unsafe.SliceData(backingArray), uintptr(len))

file := fs.mem.getFile(inode)
file.read(0, byteView)

m := &Mmap{
Data: byteView,
Inode: inode,
}

file.mmaps = append(file.mmaps, m)
return m
}

// Close the given mmap.
func (fs *Filesystem) Munmap(m *Mmap) {
fs.mu.Lock()
defer fs.mu.Unlock()

file := fs.mem.getFile(m.Inode)
idx := slices.Index(file.mmaps, m)
if idx == -1 {
panic("not found")
}
file.mmaps = slices.Delete(file.mmaps, idx, idx+1)
}
5 changes: 5 additions & 0 deletions internal/simulation/gensyscall/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ var filterCommon = []string{
"SYS_CLOSE",
"SYS_CONNECT",
"SYS_FCNTL",
"SYS_FDATASYNC",
"SYS_FLOCK",
"SYS_FSTAT",
"SYS_FSYNC",
"SYS_FTRUNCATE",
Expand All @@ -203,6 +205,9 @@ var filterCommon = []string{
"SYS_GETSOCKNAME",
"SYS_GETSOCKOPT",
"SYS_LISTEN",
"SYS_MADVISE",
"SYS_MMAP",
"SYS_MUNMAP",
"SYS_OPENAT",
"SYS_PREAD64",
"SYS_PWRITE64",
Expand Down
Loading

0 comments on commit 057c053

Please sign in to comment.