├── README.md ├── go.mod ├── go.sum ├── mmap_windows_386.go ├── mmap_windows_amd64.go ├── LICENSE ├── mmap_unix.go ├── mmap_other.go ├── example_mmap_test.go ├── .github └── workflows │ └── ci.yml ├── mmap_windows.go ├── mmap.go └── mmap_test.go /README.md: -------------------------------------------------------------------------------- 1 | # mmap 2 | 3 | **ARCHIVED**. Please go to [mmap](https://codeberg.org/go-mmap/mmap). 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-mmap/mmap 2 | 3 | go 1.19 4 | 5 | require golang.org/x/sys v0.6.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 2 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 3 | -------------------------------------------------------------------------------- /mmap_windows_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | package mmap 6 | 7 | const maxBytes = 1<<31 - 1 8 | -------------------------------------------------------------------------------- /mmap_windows_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | package mmap 6 | 7 | const maxBytes = 1<<50 - 1 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright ©2020 The go-mmap Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the go-mmap project nor the names of its authors and 11 | contributors may be used to endorse or promote products derived from this 12 | software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /mmap_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | //go:build linux || darwin || freebsd 6 | // +build linux darwin freebsd 7 | 8 | package mmap 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "runtime" 14 | 15 | syscall "golang.org/x/sys/unix" 16 | ) 17 | 18 | func openFile(filename string, fl Flag) (*File, error) { 19 | f, err := os.OpenFile(filename, fl.flag(), 0666) 20 | if err != nil { 21 | return nil, fmt.Errorf("mmap: could not open %q: %w", filename, err) 22 | } 23 | 24 | fi, err := f.Stat() 25 | if err != nil { 26 | return nil, fmt.Errorf("mmap: could not stat %q: %w", filename, err) 27 | } 28 | 29 | size := fi.Size() 30 | if size == 0 { 31 | return &File{fd: f, flag: fl, fi: fi}, nil 32 | } 33 | if size < 0 { 34 | return nil, fmt.Errorf("mmap: file %q has negative size", filename) 35 | } 36 | if size != int64(int(size)) { 37 | return nil, fmt.Errorf("mmap: file %q is too large", filename) 38 | } 39 | 40 | prot := syscall.PROT_READ 41 | if fl&Write != 0 { 42 | prot |= syscall.PROT_WRITE 43 | } 44 | 45 | data, err := syscall.Mmap(int(f.Fd()), 0, int(size), prot, syscall.MAP_SHARED) 46 | if err != nil { 47 | return nil, fmt.Errorf("mmap: could not mmap %q: %w", filename, err) 48 | } 49 | r := &File{ 50 | data: data, 51 | fd: f, 52 | flag: fl, 53 | fi: fi, 54 | } 55 | runtime.SetFinalizer(r, (*File).Close) 56 | return r, nil 57 | } 58 | 59 | // Sync commits the current contents of the file to stable storage. 60 | func (f *File) Sync() error { 61 | if !f.wflag() { 62 | return errBadFD 63 | } 64 | return syscall.Msync(f.data, syscall.MS_SYNC) 65 | } 66 | 67 | // Close closes the memory-mapped file. 68 | func (f *File) Close() error { 69 | if f.data == nil { 70 | return nil 71 | } 72 | defer f.Close() 73 | 74 | data := f.data 75 | f.data = nil 76 | runtime.SetFinalizer(f, nil) 77 | return syscall.Munmap(data) 78 | } 79 | -------------------------------------------------------------------------------- /mmap_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | //go:build !linux && !windows && !darwin && !freebsd 6 | // +build !linux,!windows,!darwin,!freebsd 7 | 8 | // Package mmap provides a way to memory-map a file. 9 | package mmap 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "os" 15 | ) 16 | 17 | // Reader reads a memory-mapped file. 18 | // 19 | // Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is 20 | // not safe to call Close and reading methods concurrently. 21 | type Reader struct { 22 | f *os.File 23 | len int 24 | } 25 | 26 | // Close closes the reader. 27 | func (r *Reader) Close() error { 28 | return r.f.Close() 29 | } 30 | 31 | // Len returns the length of the underlying memory-mapped file. 32 | func (r *Reader) Len() int { 33 | return r.len 34 | } 35 | 36 | // At returns the byte at index i. 37 | func (r *Reader) At(i int) byte { 38 | if i < 0 || r.len <= i { 39 | panic("index out of range") 40 | } 41 | var b [1]byte 42 | r.ReadAt(b[:], int64(i)) 43 | return b[0] 44 | } 45 | 46 | // Read implements the io.Reader interface. 47 | func (r *Reader) Read(p []byte) (int, error) { 48 | return r.f.Read(p) 49 | } 50 | 51 | // ReadAt implements the io.ReaderAt interface. 52 | func (r *Reader) ReadAt(p []byte, off int64) (int, error) { 53 | return r.f.ReadAt(p, off) 54 | } 55 | 56 | func (r *Reader) Seek(offset int64, whence int) (int64, error) { 57 | return r.f.Seek(offset, whence) 58 | } 59 | 60 | // Open memory-maps the named file for reading. 61 | func Open(filename string) (*Reader, error) { 62 | f, err := os.Open(filename) 63 | if err != nil { 64 | return nil, err 65 | } 66 | fi, err := f.Stat() 67 | if err != nil { 68 | f.Close() 69 | return nil, err 70 | } 71 | 72 | size := fi.Size() 73 | if size < 0 { 74 | f.Close() 75 | return nil, fmt.Errorf("mmap: file %q has negative size", filename) 76 | } 77 | if size != int64(int(size)) { 78 | f.Close() 79 | return nil, fmt.Errorf("mmap: file %q is too large", filename) 80 | } 81 | 82 | return &Reader{ 83 | f: f, 84 | len: int(fi.Size()), 85 | }, nil 86 | } 87 | 88 | var ( 89 | _ io.Reader = (*Reader)(nil) 90 | _ io.ReaderAt = (*Reader)(nil) 91 | _ io.Seeker = (*Reader)(nil) 92 | _ io.Closer = (*Reader)(nil) 93 | ) 94 | -------------------------------------------------------------------------------- /example_mmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-mmap 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 | package mmap_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "os" 11 | 12 | "github.com/go-mmap/mmap" 13 | ) 14 | 15 | func ExampleOpen() { 16 | f, err := mmap.Open("example_mmap_test.go") 17 | if err != nil { 18 | log.Fatalf("could not mmap file: %+v", err) 19 | } 20 | defer f.Close() 21 | 22 | buf := make([]byte, 32) 23 | _, err = f.Read(buf) 24 | if err != nil { 25 | log.Fatalf("could not read into buffer: %+v", err) 26 | } 27 | 28 | fmt.Printf("%s\n", buf[:12]) 29 | 30 | // Output: 31 | // // Copyright 32 | } 33 | 34 | func ExampleOpenFile_read() { 35 | f, err := mmap.OpenFile("example_mmap_test.go", mmap.Read) 36 | if err != nil { 37 | log.Fatalf("could not mmap file: %+v", err) 38 | } 39 | defer f.Close() 40 | 41 | buf := make([]byte, 32) 42 | _, err = f.ReadAt(buf, 0) 43 | if err != nil { 44 | log.Fatalf("could not read into buffer: %+v", err) 45 | } 46 | 47 | fmt.Printf("%s\n", buf[:12]) 48 | 49 | // Output: 50 | // // Copyright 51 | } 52 | 53 | func ExampleOpenFile_readwrite() { 54 | f, err := os.CreateTemp("", "mmap-") 55 | if err != nil { 56 | log.Fatalf("could not create tmp file: %+v", err) 57 | } 58 | defer f.Close() 59 | defer os.Remove(f.Name()) 60 | 61 | _, err = f.Write([]byte("hello world!")) 62 | if err != nil { 63 | log.Fatalf("could not write data: %+v", err) 64 | } 65 | 66 | err = f.Close() 67 | if err != nil { 68 | log.Fatalf("could not close file: %+v", err) 69 | } 70 | 71 | raw, err := os.ReadFile(f.Name()) 72 | if err != nil { 73 | log.Fatalf("could not read back data: %+v", err) 74 | } 75 | 76 | fmt.Printf("%s\n", raw) 77 | 78 | rw, err := mmap.OpenFile(f.Name(), mmap.Read|mmap.Write) 79 | if err != nil { 80 | log.Fatalf("could not open mmap file: %+v", err) 81 | } 82 | defer rw.Close() 83 | 84 | _, err = rw.Write([]byte("bye!")) 85 | if err != nil { 86 | log.Fatalf("could not write to mmap file: %+v", err) 87 | } 88 | 89 | raw, err = os.ReadFile(f.Name()) 90 | if err != nil { 91 | log.Fatalf("could not read back data: %+v", err) 92 | } 93 | 94 | fmt.Printf("%s\n", raw) 95 | 96 | // Output: 97 | // hello world! 98 | // bye!o world! 99 | } 100 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '0 2 * * 1-5' 10 | 11 | env: 12 | GOPROXY: "https://proxy.golang.org" 13 | TAGS: "-tags=ci" 14 | COVERAGE: "-coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/go-mmap/mmap" 15 | 16 | jobs: 17 | 18 | build: 19 | name: Build 20 | strategy: 21 | matrix: 22 | go-version: [1.20.x, 1.19.x] 23 | platform: [ubuntu-latest, macos-latest, windows-latest] 24 | runs-on: ${{ matrix.platform }} 25 | steps: 26 | - name: Install Go 27 | uses: actions/setup-go@v3 28 | with: 29 | go-version: ${{ matrix.go-version }} 30 | 31 | - name: Cache-Go 32 | uses: actions/cache@v3 33 | with: 34 | # In order: 35 | # * Module download cache 36 | # * Build cache (Linux) 37 | # * Build cache (Mac) 38 | # * Build cache (Windows) 39 | path: | 40 | ~/go/pkg/mod 41 | ~/.cache/go-build 42 | ~/Library/Caches/go-build 43 | '%LocalAppData%\go-build' 44 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 45 | restore-keys: | 46 | ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 47 | 48 | - name: Checkout code 49 | uses: actions/checkout@v3 50 | 51 | - name: Build-Linux-32b 52 | if: matrix.platform == 'ubuntu-latest' 53 | run: | 54 | GOARCH=386 go install -v $TAGS ./... 55 | - name: Build-Linux-64b 56 | if: matrix.platform == 'ubuntu-latest' 57 | run: | 58 | GOARCH=amd64 go install -v $TAGS ./... 59 | - name: Build-Windows 60 | if: matrix.platform == 'windows-latest' 61 | run: | 62 | go install -v $TAGS ./... 63 | - name: Build-Darwin 64 | if: matrix.platform == 'macos-latest' 65 | run: | 66 | go install -v $TAGS ./... 67 | - name: Test Linux 68 | if: matrix.platform == 'ubuntu-latest' 69 | run: | 70 | go test $TAGS -race $COVERAGE ./... 71 | - name: Test Windows 72 | if: matrix.platform == 'windows-latest' 73 | run: | 74 | go test $TAGS ./... 75 | - name: Test Darwin 76 | if: matrix.platform == 'macos-latest' 77 | run: | 78 | go test $TAGS ./... 79 | - name: static-check 80 | uses: dominikh/staticcheck-action@v1.2.0 81 | with: 82 | install-go: false 83 | cache-key: ${{ matrix.platform }} 84 | version: "2023.1" 85 | - name: Upload-Coverage 86 | if: matrix.platform == 'ubuntu-latest' 87 | uses: codecov/codecov-action@v2 88 | -------------------------------------------------------------------------------- /mmap_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | package mmap 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "runtime" 11 | "unsafe" 12 | 13 | syscall "golang.org/x/sys/windows" 14 | ) 15 | 16 | func openFile(filename string, fl Flag) (*File, error) { 17 | f, err := os.OpenFile(filename, fl.flag(), 0666) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | fi, err := f.Stat() 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | size := fi.Size() 28 | if size == 0 { 29 | return &File{fd: f, flag: fl, fi: fi}, nil 30 | } 31 | if size < 0 { 32 | return nil, fmt.Errorf("mmap: file %q has negative size", filename) 33 | } 34 | if size != int64(int(size)) { 35 | return nil, fmt.Errorf("mmap: file %q is too large", filename) 36 | } 37 | 38 | prot := uint32(syscall.PAGE_READONLY) 39 | view := uint32(syscall.FILE_MAP_READ) 40 | if fl&Write != 0 { 41 | prot = syscall.PAGE_READWRITE 42 | view = syscall.FILE_MAP_WRITE 43 | } 44 | 45 | low, high := uint32(size), uint32(size>>32) 46 | fmap, err := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, prot, high, low, nil) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer syscall.CloseHandle(fmap) 51 | ptr, err := syscall.MapViewOfFile(fmap, view, 0, 0, uintptr(size)) 52 | if err != nil { 53 | return nil, err 54 | } 55 | data := (*[maxBytes]byte)(unsafe.Pointer(ptr))[:size] 56 | 57 | fd := &File{ 58 | data: data, 59 | fd: f, 60 | fi: fi, 61 | flag: fl, 62 | } 63 | runtime.SetFinalizer(fd, (*File).Close) 64 | return fd, nil 65 | 66 | } 67 | 68 | // Sync commits the current contents of the file to stable storage. 69 | func (f *File) Sync() error { 70 | if !f.wflag() { 71 | return errBadFD 72 | } 73 | 74 | err := syscall.FlushViewOfFile(f.addr(), uintptr(len(f.data))) 75 | if err != nil { 76 | return fmt.Errorf("mmap: could not sync view: %w", err) 77 | } 78 | 79 | err = syscall.FlushFileBuffers(syscall.Handle(f.fd.Fd())) 80 | if err != nil { 81 | return fmt.Errorf("mmap: could not sync file buffers: %w", err) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // Close closes the reader. 88 | func (f *File) Close() error { 89 | if f.data == nil { 90 | return nil 91 | } 92 | defer f.fd.Close() 93 | 94 | addr := f.addr() 95 | f.data = nil 96 | runtime.SetFinalizer(f, nil) 97 | return syscall.UnmapViewOfFile(addr) 98 | } 99 | 100 | func (f *File) addr() uintptr { 101 | data := f.data 102 | return uintptr(unsafe.Pointer(&data[0])) 103 | } 104 | -------------------------------------------------------------------------------- /mmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | // Package mmap provides a way to memory-map a file. 6 | package mmap 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io" 12 | "os" 13 | ) 14 | 15 | var errBadFD = errors.New("bad file descriptor") 16 | 17 | // Flag specifies how a mmap file should be opened. 18 | type Flag int 19 | 20 | const ( 21 | Read Flag = 0x1 // Read enables read-access to a mmap file. 22 | Write Flag = 0x2 // Write enables write-access to a mmap file. 23 | ) 24 | 25 | func (fl Flag) flag() int { 26 | var flag int 27 | 28 | switch fl { 29 | case Read: 30 | flag = os.O_RDONLY 31 | case Write: 32 | flag = os.O_WRONLY 33 | case Read | Write: 34 | flag = os.O_RDWR 35 | } 36 | 37 | return flag 38 | } 39 | 40 | // File reads/writes a memory-mapped file. 41 | type File struct { 42 | data []byte 43 | c int 44 | 45 | fd *os.File 46 | flag Flag 47 | fi os.FileInfo 48 | } 49 | 50 | // Open memory-maps the named file for reading. 51 | func Open(filename string) (*File, error) { 52 | return openFile(filename, Read) 53 | } 54 | 55 | // OpenFile memory-maps the named file for reading/writing, depending on 56 | // the flag value. 57 | func OpenFile(filename string, flag Flag) (*File, error) { 58 | return openFile(filename, flag) 59 | } 60 | 61 | // Len returns the length of the underlying memory-mapped file. 62 | func (f *File) Len() int { 63 | return len(f.data) 64 | } 65 | 66 | // At returns the byte at index i. 67 | func (f *File) At(i int) byte { 68 | return f.data[i] 69 | } 70 | 71 | // Stat returns the FileInfo structure describing file. 72 | // If there is an error, it will be of type *os.PathError. 73 | func (f *File) Stat() (os.FileInfo, error) { 74 | if f == nil { 75 | return nil, os.ErrInvalid 76 | } 77 | 78 | return f.fi, nil 79 | } 80 | 81 | func (f *File) rflag() bool { 82 | return f.flag&Read != 0 83 | } 84 | 85 | func (f *File) wflag() bool { 86 | return f.flag&Write != 0 87 | } 88 | 89 | // Read implements the io.Reader interface. 90 | func (f *File) Read(p []byte) (int, error) { 91 | if f == nil { 92 | return 0, os.ErrInvalid 93 | } 94 | 95 | if !f.rflag() { 96 | return 0, errBadFD 97 | } 98 | if f.c >= len(f.data) { 99 | return 0, io.EOF 100 | } 101 | n := copy(p, f.data[f.c:]) 102 | f.c += n 103 | return n, nil 104 | } 105 | 106 | // ReadByte implements the io.ByteReader interface. 107 | func (f *File) ReadByte() (byte, error) { 108 | if f == nil { 109 | return 0, os.ErrInvalid 110 | } 111 | 112 | if !f.rflag() { 113 | return 0, errBadFD 114 | } 115 | if f.c >= len(f.data) { 116 | return 0, io.EOF 117 | } 118 | v := f.data[f.c] 119 | f.c++ 120 | return v, nil 121 | } 122 | 123 | // ReadAt implements the io.ReaderAt interface. 124 | func (f *File) ReadAt(p []byte, off int64) (int, error) { 125 | if f == nil { 126 | return 0, os.ErrInvalid 127 | } 128 | 129 | if !f.rflag() { 130 | return 0, errBadFD 131 | } 132 | if f.data == nil { 133 | return 0, errors.New("mmap: closed") 134 | } 135 | if off < 0 || int64(len(f.data)) < off { 136 | return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off) 137 | } 138 | n := copy(p, f.data[off:]) 139 | if n < len(p) { 140 | return n, io.EOF 141 | } 142 | return n, nil 143 | } 144 | 145 | // Write implements the io.Writer interface. 146 | func (f *File) Write(p []byte) (int, error) { 147 | if f == nil { 148 | return 0, os.ErrInvalid 149 | } 150 | 151 | if !f.wflag() { 152 | return 0, errBadFD 153 | } 154 | if f.c >= len(f.data) { 155 | return 0, io.ErrShortWrite 156 | } 157 | n := copy(f.data[f.c:], p) 158 | f.c += n 159 | if len(p) > n { 160 | return n, io.ErrShortWrite 161 | } 162 | return n, nil 163 | } 164 | 165 | // WriteByte implements the io.ByteWriter interface. 166 | func (f *File) WriteByte(c byte) error { 167 | if f == nil { 168 | return os.ErrInvalid 169 | } 170 | 171 | if !f.wflag() { 172 | return errBadFD 173 | } 174 | if f.c >= len(f.data) { 175 | return io.ErrShortWrite 176 | } 177 | f.data[f.c] = c 178 | f.c++ 179 | return nil 180 | } 181 | 182 | // WriteAt implements the io.WriterAt interface. 183 | func (f *File) WriteAt(p []byte, off int64) (int, error) { 184 | if f == nil { 185 | return 0, os.ErrInvalid 186 | } 187 | 188 | if !f.wflag() { 189 | return 0, errBadFD 190 | } 191 | if f.data == nil { 192 | return 0, errors.New("mmap: closed") 193 | } 194 | if off < 0 || int64(len(f.data)) < off { 195 | return 0, fmt.Errorf("mmap: invalid WriteAt offset %d", off) 196 | } 197 | n := copy(f.data[off:], p) 198 | if n < len(p) { 199 | return n, io.ErrShortWrite 200 | } 201 | return n, nil 202 | } 203 | 204 | func (f *File) Seek(offset int64, whence int) (int64, error) { 205 | if f == nil { 206 | return 0, os.ErrInvalid 207 | } 208 | 209 | switch whence { 210 | case io.SeekStart: 211 | f.c = int(offset) 212 | case io.SeekCurrent: 213 | f.c += int(offset) 214 | case io.SeekEnd: 215 | f.c = len(f.data) - int(offset) 216 | default: 217 | return 0, fmt.Errorf("mmap: invalid whence") 218 | } 219 | if f.c < 0 { 220 | return 0, fmt.Errorf("mmap: negative position") 221 | } 222 | return int64(f.c), nil 223 | } 224 | 225 | var ( 226 | _ io.Reader = (*File)(nil) 227 | _ io.ReaderAt = (*File)(nil) 228 | _ io.ByteReader = (*File)(nil) 229 | _ io.Writer = (*File)(nil) 230 | _ io.WriterAt = (*File)(nil) 231 | _ io.ByteWriter = (*File)(nil) 232 | _ io.Closer = (*File)(nil) 233 | _ io.Seeker = (*File)(nil) 234 | ) 235 | -------------------------------------------------------------------------------- /mmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | package mmap 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | ) 14 | 15 | func TestOpen(t *testing.T) { 16 | const filename = "mmap_test.go" 17 | for _, tc := range []struct { 18 | name string 19 | open func(fname string) (*File, error) 20 | }{ 21 | { 22 | name: "open", 23 | open: Open, 24 | }, 25 | { 26 | name: "open-read-only", 27 | open: func(fname string) (*File, error) { 28 | return OpenFile(fname, Read) 29 | }, 30 | }, 31 | } { 32 | t.Run(tc.name, func(t *testing.T) { 33 | r, err := Open(filename) 34 | if err != nil { 35 | t.Fatalf("Open: %+v", err) 36 | } 37 | defer r.Close() 38 | 39 | _, err = r.Stat() 40 | if err != nil { 41 | t.Fatalf("could not stat file: %+v", err) 42 | } 43 | 44 | if !r.rflag() { 45 | t.Fatal("not open for reading") 46 | } 47 | 48 | got := make([]byte, r.Len()) 49 | if _, err := r.ReadAt(got, 0); err != nil && err != io.EOF { 50 | t.Fatalf("ReadAt: %v", err) 51 | } 52 | want, err := os.ReadFile(filename) 53 | if err != nil { 54 | t.Fatalf("os.ReadFile: %v", err) 55 | } 56 | if len(got) != len(want) { 57 | t.Fatalf("got %d bytes, want %d", len(got), len(want)) 58 | } 59 | if !bytes.Equal(got, want) { 60 | t.Fatalf("\ngot %q\nwant %q", string(got), string(want)) 61 | } 62 | 63 | t.Run("Read", func(t *testing.T) { 64 | got := make([]byte, 32) 65 | _, err := io.ReadFull(r, got) 66 | if err != nil { 67 | t.Fatalf("%+v", err) 68 | } 69 | 70 | if got, want := got, want[:len(got)]; !bytes.Equal(got, want) { 71 | t.Fatalf("invalid Read: got=%q, want=%q", got, want) 72 | } 73 | 74 | pos, err := r.Seek(0, io.SeekCurrent) 75 | if err != nil { 76 | t.Fatalf("could not seek: %+v", err) 77 | } 78 | if got, want := pos, int64(32); got != want { 79 | t.Fatalf("invalid position: got=%d, want=%d", got, want) 80 | } 81 | }) 82 | 83 | t.Run("At", func(t *testing.T) { 84 | got := r.At(32) 85 | if got, want := got, want[32]; got != want { 86 | t.Fatalf("invalid At: got=%q, want=%q", got, want) 87 | } 88 | }) 89 | 90 | t.Run("ReadByte", func(t *testing.T) { 91 | _, err := r.Seek(32, io.SeekStart) 92 | if err != nil { 93 | t.Fatalf("could not seek: %+v", err) 94 | } 95 | 96 | got, err := r.ReadByte() 97 | if err != nil { 98 | t.Fatalf("could not read byte: %+v", err) 99 | } 100 | 101 | if got, want := got, want[32]; got != want { 102 | t.Fatalf("invalid byte: got=%q, want=%q", got, want) 103 | } 104 | }) 105 | 106 | t.Run("Seek", func(t *testing.T) { 107 | _, err := r.Seek(32, io.SeekStart) 108 | if err != nil { 109 | t.Fatalf("could not seek: %+v", err) 110 | } 111 | 112 | got, err := r.ReadByte() 113 | if err != nil { 114 | t.Fatalf("could not read byte: %+v", err) 115 | } 116 | 117 | if got, want := got, want[32]; got != want { 118 | t.Fatalf("invalid byte: got=%q, want=%q", got, want) 119 | } 120 | 121 | _, err = r.Seek(32, io.SeekCurrent) 122 | if err != nil { 123 | t.Fatalf("could not seek: %+v", err) 124 | } 125 | 126 | got, err = r.ReadByte() 127 | if err != nil { 128 | t.Fatalf("could not read byte: %+v", err) 129 | } 130 | 131 | if got, want := got, want[64+1]; got != want { 132 | t.Fatalf("invalid byte: got=%q, want=%q", got, want) 133 | } 134 | 135 | _, err = r.Seek(32, io.SeekEnd) 136 | if err != nil { 137 | t.Fatalf("could not seek: %+v", err) 138 | } 139 | 140 | got, err = r.ReadByte() 141 | if err != nil { 142 | t.Fatalf("could not read byte: %+v", err) 143 | } 144 | 145 | if got, want := got, want[len(want)-32]; got != want { 146 | t.Fatalf("invalid byte: got=%q, want=%q", got, want) 147 | } 148 | 149 | }) 150 | 151 | t.Run("sync", func(t *testing.T) { 152 | err := r.Sync() 153 | if err == nil { 154 | t.Fatal("expected an error") 155 | } 156 | if got, want := err, errBadFD; got.Error() != want.Error() { 157 | t.Fatalf("invalid error:\ngot= %+v\nwant=%+v", got, want) 158 | } 159 | }) 160 | 161 | t.Run("write", func(t *testing.T) { 162 | _, err = r.Write([]byte("hello")) 163 | if err == nil { 164 | t.Fatal("expected an error") 165 | } 166 | if got, want := err, errBadFD; got.Error() != want.Error() { 167 | t.Fatalf("invalid error:\ngot= %+v\nwant=%+v", got, want) 168 | } 169 | }) 170 | 171 | t.Run("write-at", func(t *testing.T) { 172 | _, err = r.WriteAt([]byte("hello"), 0) 173 | if err == nil { 174 | t.Fatal("expected an error") 175 | } 176 | if got, want := err, errBadFD; got.Error() != want.Error() { 177 | t.Fatalf("invalid error:\ngot= %+v\nwant=%+v", got, want) 178 | } 179 | }) 180 | 181 | t.Run("write-byte", func(t *testing.T) { 182 | err = r.WriteByte('h') 183 | if err == nil { 184 | t.Fatal("expected an error") 185 | } 186 | if got, want := err, errBadFD; got.Error() != want.Error() { 187 | t.Fatalf("invalid error:\ngot= %+v\nwant=%+v", got, want) 188 | } 189 | }) 190 | 191 | err = r.Close() 192 | if err != nil { 193 | t.Fatalf("could not close mmap reader: %+v", err) 194 | } 195 | }) 196 | } 197 | } 198 | 199 | func TestWrite(t *testing.T) { 200 | tmp, err := os.MkdirTemp("", "mmap-") 201 | if err != nil { 202 | t.Fatalf("could not create temp dir: %+v", err) 203 | } 204 | defer os.RemoveAll(tmp) 205 | 206 | display := func(fname string) []byte { 207 | t.Helper() 208 | raw, err := os.ReadFile(fname) 209 | if err != nil { 210 | t.Fatalf("could not read file %q: %+v", fname, err) 211 | } 212 | return raw 213 | } 214 | 215 | for _, tc := range []struct { 216 | name string 217 | flags Flag 218 | }{ 219 | // { 220 | // name: "write-only", 221 | // flags: Write, 222 | // }, 223 | { 224 | name: "read-write", 225 | flags: Read | Write, 226 | }, 227 | } { 228 | t.Run(tc.name, func(t *testing.T) { 229 | fname := filepath.Join(tmp, tc.name+".txt") 230 | err := os.WriteFile(fname, []byte("hello world!\nbye.\n"), 0644) 231 | if err != nil { 232 | t.Fatalf("could not seed file: %+v", err) 233 | } 234 | 235 | f, err := OpenFile(fname, tc.flags) 236 | if err != nil { 237 | t.Fatalf("could not mmap file: %+v", err) 238 | } 239 | defer f.Close() 240 | 241 | _, err = f.WriteAt([]byte("bye!\n"), 3) 242 | if err != nil { 243 | t.Fatalf("could not write-at: %+v", err) 244 | } 245 | 246 | if got, want := display(fname), []byte("helbye!\nrld!\nbye.\n"); !bytes.Equal(got, want) { 247 | t.Fatalf("invalid content:\ngot= %q\nwant=%q\n", got, want) 248 | } 249 | 250 | _, err = f.Seek(0, io.SeekStart) 251 | if err != nil { 252 | t.Fatalf("could not seek to start: %+v", err) 253 | } 254 | 255 | _, err = f.Write([]byte("hello world!\nbye\n")) 256 | if err != nil { 257 | t.Fatalf("could not write: %+v", err) 258 | } 259 | 260 | if got, want := display(fname), []byte("hello world!\nbye\n\n"); !bytes.Equal(got, want) { 261 | t.Fatalf("invalid content:\ngot= %q\nwant=%q\n", got, want) 262 | } 263 | 264 | _, err = f.Seek(5, io.SeekEnd) 265 | if err != nil { 266 | t.Fatalf("could not seek from end: %+v", err) 267 | } 268 | 269 | err = f.WriteByte('t') 270 | if err != nil { 271 | t.Fatalf("could not write-byte: %+v", err) 272 | } 273 | 274 | err = f.Sync() 275 | if err != nil { 276 | t.Fatalf("could not sync mmap file: %+v", err) 277 | } 278 | 279 | if got, want := display(fname), []byte("hello world!\ntye\n\n"); !bytes.Equal(got, want) { 280 | t.Fatalf("invalid content:\ngot= %q\nwant=%q\n", got, want) 281 | } 282 | 283 | }) 284 | } 285 | } 286 | --------------------------------------------------------------------------------