Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
edsrzf committed Apr 2, 2011
0 parents commit b84771a
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
*.out
*.5
*.6
*.8
*.swp
_obj
_test
testdata
25 changes: 25 additions & 0 deletions LICENSE
@@ -0,0 +1,25 @@
Copyright (c) 2011, Evan Shaw <edsrzf@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

37 changes: 37 additions & 0 deletions Makefile
@@ -0,0 +1,37 @@
# Copyright 2011 Evan Shaw. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

include $(GOROOT)/src/Make.inc

TARG=github.com/edsrzf/mmap-go
GOFILES=\
const_unix.go\
mmap.go\

GOFILES_freebsd=\
mmap_unix.go\
mmap_unix_syscall.go

GOFILES_darwin=\
mmap_unix.go\
mmap_unix_syscall.go

GOFILES_linux=\
mmap_unix.go
ifeq ($(GOARCH),amd64)
GOFILES_linux+=\
mmap_unix_syscall.go
else
GOFILES_linux+=\
mmap_linux32_syscall.go
endif

GOFILES_windows=\
mmap_windows.go

GOFILES+=$(GOFILES_$(GOOS))
include $(GOROOT)/src/Make.pkg

const_unix.go: const_unix.c
godefs -gmmap const_unix.c > const_unix.go
12 changes: 12 additions & 0 deletions README.md
@@ -0,0 +1,12 @@
mmap-go
=======

mmap-go is a portable mmap package for the [Go programming language](http://golang.org).
It has been tested on Linux (386, amd64) and Windows (386). It should also work on
other Unix-like platforms, but hasn't been tested with them. I'm interested to
hear about the results.

I haven't been able to add more features without adding significant complexity,
so mmap-go doesn't support as many features as a non-portable package might.
If you're running on a Unix-like platform and need some of these features,
I suggest Gustavo Niemeyer's [gommap](http://labix.org/gommap).
17 changes: 17 additions & 0 deletions const_unix.c
@@ -0,0 +1,17 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include <sys/mman.h>

enum {
$_PROT_READ = PROT_READ,
$_PROT_WRITE = PROT_WRITE,
$_PROT_EXEC = PROT_EXEC,

$_MAP_ANONYMOUS = MAP_ANONYMOUS,
$_MAP_SHARED = MAP_SHARED,
$_MAP_PRIVATE = MAP_PRIVATE,

$_MS_SYNC = MS_SYNC,
};
18 changes: 18 additions & 0 deletions const_unix.go
@@ -0,0 +1,18 @@
// godefs -gmmap const_unix.c

// MACHINE GENERATED - DO NOT EDIT.

package mmap

// Constants
const (
_PROT_READ = 0x1;
_PROT_WRITE = 0x2;
_PROT_EXEC = 0x4;
_MAP_ANONYMOUS = 0x20;
_MAP_SHARED = 0x1;
_MAP_PRIVATE = 0x2;
_MS_SYNC = 0x4;
)

// Types
122 changes: 122 additions & 0 deletions mmap.go
@@ -0,0 +1,122 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file defines the common package interface and contains a little bit of
// factored out logic.

// Package mmap allows mapping files into memory. It tries to provide a simple, reasonably portable interface,
// but doesn't go out of its way to abstract away every little platform detail.
// This specifically means:
// * forked processes may or may not inherit mappings
// * a file's timestamp may or may not be updated by writes through mappings
// * specifying a size larger than the file's actual size can increase the file's size
// * If the mapped file is being modified by another process while your program's running, don't expect consistent results between platforms
package mmap

import (
"os"
"reflect"
"unsafe"
)

const (
// RDONLY maps the memory read-only.
// Attempts to write to the MMap object will result in undefined behavior.
RDONLY = 0
// RDWR maps the memory as read-write. Writes to the MMap object will update the
// underlying file.
RDWR = 1 << iota
// COPY maps the memory as copy-on-write. Writes to the MMap object will affect
// memory, but the underlying file will remain unchanged.
COPY
// If EXEC is set, the mapped memory is marked as executable.
EXEC
)

const (
// If the ANON flag is set, the mapped memory will not be backed by a file.
ANON = 1 << iota
)

// MMap represents a file mapped into memory.
type MMap []byte

// Map maps an entire file into memory.
// If ANON is set in flags, f is ignored.
func Map(f *os.File, prot, flags int) (MMap, os.Error) {
return MapRegion(f, -1, prot, flags, 0)
}

// MapRegion maps part of a file into memory.
// The offset parameter must be a multiple of the system's page size.
// If length < 0, the entire file will be mapped.
// If ANON is set in flags, f is ignored.
func MapRegion(f *os.File, length int64, prot, flags int, offset int64) (MMap, os.Error) {
var fd uintptr
if flags&ANON == 0 {
fd = uintptr(f.Fd())
if length < 0 {
fi, err := f.Stat()
if err != nil {
return nil, err
}
length = fi.Size
}
} else {
if length <= 0 {
return nil, os.NewError("anonymous mapping requires non-zero length")
}
fd = ^uintptr(0)
}
addr, err := mmap(length, uintptr(prot), uintptr(flags), fd, offset)
if err != nil {
return nil, err
}

m := MMap{}
dh := m.header()
dh.Data = addr
// TODO: eek, truncation
dh.Len = int(length)
dh.Cap = dh.Len
return m, nil
}

func (m *MMap) header() *reflect.SliceHeader {
return (*reflect.SliceHeader)(unsafe.Pointer(m))
}

// Lock keeps the mapped region in physical memory, ensuring that it will not be
// swapped out.
func (m MMap) Lock() os.Error {
dh := m.header()
return lock(dh.Data, uintptr(dh.Len))
}

// Unlock reverses the effect of Lock, allowing the mapped region to potentially
// be swapped out.
// If m is already unlocked, aan error will result.
func (m MMap) Unlock() os.Error {
dh := m.header()
return unlock(dh.Data, uintptr(dh.Len))
}

// Flush synchronizes the mapping's contents to the file's contents on disk.
func (m MMap) Flush() os.Error {
dh := m.header()
return flush(dh.Data, uintptr(dh.Len))
}

// Unmap deletes the memory mapped region, flushes any remaining changes, and sets
// m to nil.
// Trying to read or write any remaining references to m after Unmap is called will
// result in undefined behavior.
// Unmap should only be called on the slice value that was originally returned from
// a call to Map. Calling Unmap on a derived slice may cause errors.
func (m *MMap) Unmap() os.Error {
dh := m.header()
err := unmap(dh.Data, uintptr(dh.Len))
*m = nil
return err
}
25 changes: 25 additions & 0 deletions mmap_linux32_syscall.go
@@ -0,0 +1,25 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mmap

// 32-bit Linux architectures (namely x86 and ARM) have two mmap system calls, both
// of which are incompatible with mmap on other platforms:
// - syscall.MMAP (old_mmap) expects a single pointer argument
// - syscall.MMAP2 (mmap2) takes an offset in pages, not bytes
// Thus, 32-bit Linux gets its own special file.
import (
"syscall"
)

func mmap_syscall(len, prot, flags, fd uintptr, off int64) (uintptr, uintptr) {
// assuming page size is 4096; the runtime does it, so it should be okay
if off&0xFFF != 0 {
return 0, syscall.EINVAL
}
off >>= 12
ptr, _, errno := syscall.Syscall6(syscall.SYS_MMAP2, 0, len, prot,
flags, fd, uintptr(off))
return ptr, errno
}
102 changes: 102 additions & 0 deletions mmap_test.go
@@ -0,0 +1,102 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// These tests are adapted from gommap: http://labix.org/gommap
// Copyright (c) 2010, Gustavo Niemeyer <gustavo@niemeyer.net>

package mmap

import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)

var testData = []byte("0123456789ABCDEF")
var testPath = filepath.Join(os.TempDir(), "testdata")

func init() {
f := openFile(os.O_RDWR|os.O_CREATE)
f.Write(testData)
f.Close()
}

func openFile(flags int) *os.File {
f, err := os.Open(testPath, flags, 0644)
if err != nil {
panic(err.String())
}
return f
}

func TestUnmap(t *testing.T) {
f := openFile(os.O_RDONLY)
defer f.Close()
mmap, err := Map(f, RDONLY, 0)
if err != nil {
t.Errorf("error mapping: %s", err)
}
if err := mmap.Unmap(); err != nil {
t.Errorf("error unmapping: %s", err)
}
}

func TestReadWrite(t *testing.T) {
f := openFile(os.O_RDWR)
defer f.Close()
mmap, err := Map(f, RDWR, 0)
if err != nil {
t.Errorf("error mapping: %s", err)
}
defer mmap.Unmap()
if !bytes.Equal(testData, mmap) {
t.Errorf("mmap != testData: %q, %q", mmap, testData)
}

mmap[9] = 'X'
mmap.Flush()

fileData, err := ioutil.ReadAll(f)
if err != nil {
t.Errorf("error reading file: %s", err)
}
if !bytes.Equal(fileData, []byte("012345678XABCDEF")) {
t.Errorf("file wasn't modified")
}

// leave things how we found them
mmap[9] = '9'
mmap.Flush()
}

func TestProtFlagsAndErr(t *testing.T) {
f := openFile(os.O_RDONLY)
defer f.Close()
if _, err := Map(f, RDWR, 0); err == nil {
t.Errorf("expected error")
}
}

func TestFlags(t *testing.T) {
f := openFile(os.O_RDWR)
defer f.Close()
mmap, err := Map(f, COPY, 0)
if err != nil {
t.Errorf("error mapping: %s", err)
}
defer mmap.Unmap()

mmap[9] = 'X'
mmap.Flush()

fileData, err := ioutil.ReadAll(f)
if err != nil {
t.Errorf("error reading file: %s", err)
}
if !bytes.Equal(fileData, testData) {
t.Errorf("file was modified")
}
}

0 comments on commit b84771a

Please sign in to comment.