| // 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. |
| |
| //go:build linux || darwin |
| |
| // 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 |
| } else if len(r.data) == 0 { |
| 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 { |
| // Treat (size == 0) as a special case, avoiding the syscall, since |
| // "man 2 mmap" says "the length... must be greater than 0". |
| // |
| // As we do not call syscall.Mmap, there is no need to call |
| // runtime.SetFinalizer to enforce a balancing syscall.Munmap. |
| return &ReaderAt{ |
| data: make([]byte, 0), |
| }, 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 |
| } |