| // Copyright 2016 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 darwin linux |
| |
| package core |
| |
| import ( |
| "errors" |
| "fmt" |
| "io" |
| "os" |
| "syscall" |
| ) |
| |
| var errMmapClosed = errors.New("mmap: closed") |
| |
| // mmapFile wraps a memory-mapped file. |
| type mmapFile struct { |
| data []byte |
| pos uint64 |
| writable bool |
| } |
| |
| // mmapOpen opens the named file for reading. |
| // If writable is true, the file is also open for writing. |
| func mmapOpen(filename string, writable bool) (*mmapFile, error) { |
| f, err := os.Open(filename) |
| if err != nil { |
| return nil, err |
| } |
| defer f.Close() |
| st, err := f.Stat() |
| if err != nil { |
| return nil, err |
| } |
| |
| size := st.Size() |
| if size == 0 { |
| return &mmapFile{data: []byte{}}, nil |
| } |
| if size < 0 { |
| return nil, fmt.Errorf("mmap: file %q has negative size: %d", filename, size) |
| } |
| if size != int64(int(size)) { |
| return nil, fmt.Errorf("mmap: file %q is too large", filename) |
| } |
| |
| prot := syscall.PROT_READ |
| if writable { |
| prot |= syscall.PROT_WRITE |
| } |
| data, err := syscall.Mmap(int(f.Fd()), 0, int(size), prot, syscall.MAP_SHARED) |
| if err != nil { |
| return nil, err |
| } |
| return &mmapFile{data: data, writable: writable}, nil |
| } |
| |
| // Size returns the size of the mapped file. |
| func (f *mmapFile) Size() uint64 { |
| return uint64(len(f.data)) |
| } |
| |
| // Pos returns the current file pointer. |
| func (f *mmapFile) Pos() uint64 { |
| return f.pos |
| } |
| |
| // SeekTo sets the current file pointer relative to the start of the file. |
| func (f *mmapFile) SeekTo(offset uint64) { |
| f.pos = offset |
| } |
| |
| // Read implements io.Reader. |
| func (f *mmapFile) Read(p []byte) (int, error) { |
| if f.data == nil { |
| return 0, errMmapClosed |
| } |
| if f.pos >= f.Size() { |
| return 0, io.EOF |
| } |
| n := copy(p, f.data[f.pos:]) |
| f.pos += uint64(n) |
| if n < len(p) { |
| return n, io.EOF |
| } |
| return n, nil |
| } |
| |
| // ReadByte implements io.ByteReader. |
| func (f *mmapFile) ReadByte() (byte, error) { |
| if f.data == nil { |
| return 0, errMmapClosed |
| } |
| if f.pos >= f.Size() { |
| return 0, io.EOF |
| } |
| b := f.data[f.pos] |
| f.pos++ |
| return b, nil |
| } |
| |
| // ReadSlice returns a slice of size n that points directly at the |
| // underlying mapped file. There is no copying. Fails if it cannot |
| // read at least n bytes. |
| func (f *mmapFile) ReadSlice(n uint64) ([]byte, error) { |
| if f.data == nil { |
| return nil, errMmapClosed |
| } |
| if f.pos+n >= f.Size() { |
| return nil, io.EOF |
| } |
| first := f.pos |
| f.pos += n |
| return f.data[first:f.pos:f.pos], nil |
| } |
| |
| // ReadSliceAt is like ReadSlice, but reads from a specific offset. |
| // The file pointer is not used or advanced. |
| func (f *mmapFile) ReadSliceAt(offset, n uint64) ([]byte, error) { |
| if f.data == nil { |
| return nil, errMmapClosed |
| } |
| if f.Size() < offset { |
| return nil, fmt.Errorf("mmap: out-of-bounds ReadSliceAt offset %d, size is %d", offset, f.Size()) |
| } |
| if offset+n >= f.Size() { |
| return nil, io.EOF |
| } |
| end := offset + n |
| return f.data[offset:end:end], nil |
| } |
| |
| // Close closes the file. |
| func (f *mmapFile) Close() error { |
| if f.data == nil { |
| return nil |
| } |
| err := syscall.Munmap(f.data) |
| f.data = nil |
| f.pos = 0 |
| return err |
| } |