mmap: a new package for memory-mapping files.
Change-Id: Ibd3f7489b06cfa22c01a388516989f29b233bd9e
Reviewed-on: https://go-review.googlesource.com/13950
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/mmap/manual_test_program.go b/mmap/manual_test_program.go
new file mode 100644
index 0000000..1278afd
--- /dev/null
+++ b/mmap/manual_test_program.go
@@ -0,0 +1,54 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+//
+// This build tag means that "go build" does not build this file. Use "go run
+// manual_test_program.go" to run it.
+//
+// You will also need to change "debug = false" to "debug = true" in mmap_*.go.
+
+package main
+
+import (
+ "log"
+ "math/rand"
+ "time"
+
+ "golang.org/x/exp/mmap"
+)
+
+var garbage []byte
+
+func main() {
+ const filename = "manual_test_program.go"
+
+ for _, explicitClose := range []bool{false, true} {
+ r, err := mmap.Open(filename)
+ if err != nil {
+ log.Fatalf("Open: %v", err)
+ }
+ if explicitClose {
+ r.Close()
+ } else {
+ // Leak the *mmap.ReaderAt returned by mmap.Open. The finalizer
+ // should pick it up, if finalizers run at all.
+ }
+ }
+
+ println("Finished all explicit Close calls.")
+ println("Creating and collecting garbage.")
+ println("Look for two munmap log messages.")
+ println("Hit Ctrl-C to exit.")
+
+ rng := rand.New(rand.NewSource(1))
+ now := time.Now()
+ for {
+ garbage = make([]byte, rng.Intn(1<<20))
+ if time.Since(now) > 1*time.Second {
+ now = time.Now()
+ print(".")
+ }
+ }
+}
diff --git a/mmap/mmap_linux.go b/mmap/mmap_linux.go
new file mode 100644
index 0000000..57f87ad
--- /dev/null
+++ b/mmap/mmap_linux.go
@@ -0,0 +1,114 @@
+// Copyright 2015 The Go Authors. 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 provides a way to memory-map a file.
+package mmap
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "syscall"
+)
+
+// debug is whether to print debugging messages for manual testing.
+//
+// The runtime.SetFinalizer documentation says that, "The finalizer for x is
+// scheduled to run at some arbitrary time after x becomes unreachable. There
+// is no guarantee that finalizers will run before a program exits", so we
+// cannot automatically test that the finalizer runs. Instead, set this to true
+// when running the manual test.
+const debug = false
+
+// ReaderAt reads a memory-mapped file.
+//
+// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
+// not safe to call Close and reading methods concurrently.
+type ReaderAt struct {
+ data []byte
+}
+
+// Close closes the reader.
+func (r *ReaderAt) Close() error {
+ if r.data == nil {
+ return nil
+ }
+ data := r.data
+ r.data = nil
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("munmap", r, p)
+ }
+ runtime.SetFinalizer(r, nil)
+ return syscall.Munmap(data)
+}
+
+// Len returns the length of the underlying memory-mapped file.
+func (r *ReaderAt) Len() int {
+ return len(r.data)
+}
+
+// At returns the byte at index i.
+func (r *ReaderAt) At(i int) byte {
+ return r.data[i]
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
+ if r.data == nil {
+ return 0, errors.New("mmap: closed")
+ }
+ if off < 0 || int64(len(r.data)) < off {
+ return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
+ }
+ n := copy(p, r.data[off:])
+ if n < len(p) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// Open memory-maps the named file for reading.
+func Open(filename string) (*ReaderAt, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ size := fi.Size()
+ if size == 0 {
+ return &ReaderAt{}, nil
+ }
+ if size < 0 {
+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)
+ }
+ if size != int64(int(size)) {
+ return nil, fmt.Errorf("mmap: file %q is too large", filename)
+ }
+
+ data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
+ if err != nil {
+ return nil, err
+ }
+ r := &ReaderAt{data}
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("mmap", r, p)
+ }
+ runtime.SetFinalizer(r, (*ReaderAt).Close)
+ return r, nil
+}
diff --git a/mmap/mmap_other.go b/mmap/mmap_other.go
new file mode 100644
index 0000000..42eee49
--- /dev/null
+++ b/mmap/mmap_other.go
@@ -0,0 +1,75 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !linux
+
+// Package mmap provides a way to memory-map a file.
+package mmap
+
+import (
+ "fmt"
+ "os"
+)
+
+// ReaderAt reads a memory-mapped file.
+//
+// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
+// not safe to call Close and reading methods concurrently.
+type ReaderAt struct {
+ f *os.File
+ len int
+}
+
+// Close closes the reader.
+func (r *ReaderAt) Close() error {
+ return r.f.Close()
+}
+
+// Len returns the length of the underlying memory-mapped file.
+func (r *ReaderAt) Len() int {
+ return r.len
+}
+
+// At returns the byte at index i.
+func (r *ReaderAt) At(i int) byte {
+ if i < 0 || r.len <= i {
+ panic("index out of range")
+ }
+ var b [1]byte
+ r.ReadAt(b[:], int64(i))
+ return b[0]
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
+ return r.f.ReadAt(p, off)
+}
+
+// Open memory-maps the named file for reading.
+func Open(filename string) (*ReaderAt, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ fi, err := f.Stat()
+ if err != nil {
+ f.Close()
+ return nil, err
+ }
+
+ size := fi.Size()
+ if size < 0 {
+ f.Close()
+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)
+ }
+ if size != int64(int(size)) {
+ f.Close()
+ return nil, fmt.Errorf("mmap: file %q is too large", filename)
+ }
+
+ return &ReaderAt{
+ f: f,
+ len: int(fi.Size()),
+ }, nil
+}
diff --git a/mmap/mmap_test.go b/mmap/mmap_test.go
new file mode 100644
index 0000000..797fc5f
--- /dev/null
+++ b/mmap/mmap_test.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Go Authors. 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
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+func TestOpen(t *testing.T) {
+ const filename = "mmap_test.go"
+ r, err := Open(filename)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+ got := make([]byte, r.Len())
+ if _, err := r.ReadAt(got, 0); err != nil && err != io.EOF {
+ t.Fatalf("ReadAt: %v", err)
+ }
+ want, err := ioutil.ReadFile(filename)
+ if err != nil {
+ t.Fatalf("ioutil.ReadFile: %v", err)
+ }
+ if len(got) != len(want) {
+ t.Fatalf("got %d bytes, want %d", len(got), len(want))
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("\ngot %q\nwant %q", string(got), string(want))
+ }
+}