1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build darwin linux
6
7package core
8
9import (
10	"errors"
11	"fmt"
12	"io"
13	"os"
14	"syscall"
15)
16
17var errMmapClosed = errors.New("mmap: closed")
18
19// mmapFile wraps a memory-mapped file.
20type mmapFile struct {
21	data     []byte
22	pos      uint64
23	writable bool
24}
25
26// mmapOpen opens the named file for reading.
27// If writable is true, the file is also open for writing.
28func mmapOpen(filename string, writable bool) (*mmapFile, error) {
29	f, err := os.Open(filename)
30	if err != nil {
31		return nil, err
32	}
33	defer f.Close()
34	st, err := f.Stat()
35	if err != nil {
36		return nil, err
37	}
38
39	size := st.Size()
40	if size == 0 {
41		return &mmapFile{data: []byte{}}, nil
42	}
43	if size < 0 {
44		return nil, fmt.Errorf("mmap: file %q has negative size: %d", filename, size)
45	}
46	if size != int64(int(size)) {
47		return nil, fmt.Errorf("mmap: file %q is too large", filename)
48	}
49
50	prot := syscall.PROT_READ
51	if writable {
52		prot |= syscall.PROT_WRITE
53	}
54	data, err := syscall.Mmap(int(f.Fd()), 0, int(size), prot, syscall.MAP_SHARED)
55	if err != nil {
56		return nil, err
57	}
58	return &mmapFile{data: data, writable: writable}, nil
59}
60
61// Size returns the size of the mapped file.
62func (f *mmapFile) Size() uint64 {
63	return uint64(len(f.data))
64}
65
66// Pos returns the current file pointer.
67func (f *mmapFile) Pos() uint64 {
68	return f.pos
69}
70
71// SeekTo sets the current file pointer relative to the start of the file.
72func (f *mmapFile) SeekTo(offset uint64) {
73	f.pos = offset
74}
75
76// Read implements io.Reader.
77func (f *mmapFile) Read(p []byte) (int, error) {
78	if f.data == nil {
79		return 0, errMmapClosed
80	}
81	if f.pos >= f.Size() {
82		return 0, io.EOF
83	}
84	n := copy(p, f.data[f.pos:])
85	f.pos += uint64(n)
86	if n < len(p) {
87		return n, io.EOF
88	}
89	return n, nil
90}
91
92// ReadByte implements io.ByteReader.
93func (f *mmapFile) ReadByte() (byte, error) {
94	if f.data == nil {
95		return 0, errMmapClosed
96	}
97	if f.pos >= f.Size() {
98		return 0, io.EOF
99	}
100	b := f.data[f.pos]
101	f.pos++
102	return b, nil
103}
104
105// ReadSlice returns a slice of size n that points directly at the
106// underlying mapped file. There is no copying. Fails if it cannot
107// read at least n bytes.
108func (f *mmapFile) ReadSlice(n uint64) ([]byte, error) {
109	if f.data == nil {
110		return nil, errMmapClosed
111	}
112	if f.pos+n >= f.Size() {
113		return nil, io.EOF
114	}
115	first := f.pos
116	f.pos += n
117	return f.data[first:f.pos:f.pos], nil
118}
119
120// ReadSliceAt is like ReadSlice, but reads from a specific offset.
121// The file pointer is not used or advanced.
122func (f *mmapFile) ReadSliceAt(offset, n uint64) ([]byte, error) {
123	if f.data == nil {
124		return nil, errMmapClosed
125	}
126	if f.Size() < offset {
127		return nil, fmt.Errorf("mmap: out-of-bounds ReadSliceAt offset %d, size is %d", offset, f.Size())
128	}
129	if offset+n >= f.Size() {
130		return nil, io.EOF
131	}
132	end := offset + n
133	return f.data[offset:end:end], nil
134}
135
136// Close closes the file.
137func (f *mmapFile) Close() error {
138	if f.data == nil {
139		return nil
140	}
141	err := syscall.Munmap(f.data)
142	f.data = nil
143	f.pos = 0
144	return err
145}
146