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))
+	}
+}