diff --git a/mmap/mmap.go b/mmap/mmap.go new file mode 100644 index 0000000..b14e753 --- /dev/null +++ b/mmap/mmap.go @@ -0,0 +1,95 @@ +package mt + +import ( + "fmt" + "os" + "reflect" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// Map creates a memory map of the given file. +func Map( + prot int, // Memory protection bitmask. See syscall.PROT_* flags. + flags int, // See syscall.MAP_* flags. MAP_SHARED is usually fine. + length int64, + f *os.File, +) ( + []byte, + error, +) { + + addr, _, errno := syscall.Syscall6( + syscall.SYS_MMAP, + 0, // addr: 0 => allow kernel to choose + uintptr(length), + uintptr(prot), + uintptr(flags), + f.Fd(), + 0) // offset: 0 => start of file + if errno != 0 { + return nil, syscall.Errno(errno) + } + + var b []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + hdr.Data = addr + hdr.Cap = int(length) + hdr.Len = 0 + + return b, nil +} + +// Unmap unmaps the data obtained by Map. +func Unmap(data []byte) error { + _, _, errno := syscall.Syscall( + syscall.SYS_MUNMAP, + uintptr(unsafe.Pointer(&data[:1][0])), + uintptr(cap(data)), + 0) + if errno != 0 { + return syscall.Errno(errno) + } + return nil +} + +// Remap truncates the file to `newSize` and returns a new memory map for the +// file. +func Remap( + f *os.File, + data []byte, + newSize int64, +) ([]byte, error) { + const mRemapMayMove = 0x1 + + if err := f.Truncate(newSize); err != nil { + return data, err + } + + addr, size, errno := unix.Syscall6( + unix.SYS_MREMAP, + uintptr(unsafe.Pointer(&data[:1][0])), + uintptr(cap(data)), + uintptr(newSize), + uintptr(mRemapMayMove), + 0, + 0) + + if errno != 0 { + return data, syscall.Errno(errno) + } + + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + hdr.Data = addr + hdr.Cap = int(newSize) + hdr.Len = int(newSize) + + if int64(size) != newSize { + panic(fmt.Sprintf("mremap returned incorrect size %d != %d", + size, newSize)) + } + + return data, nil +}