├── LICENSE ├── README.md ├── consts.c.txt ├── consts.go ├── consts.sh ├── consts_windows.go ├── go.mod ├── go.sum ├── gommap.go ├── gommap_test.go ├── gommap_twopage_test.go ├── gommap_windows.go ├── gommap_windows_test.go ├── mmap_darwin_amd64.go ├── mmap_darwin_arm64.go ├── mmap_freebsd_amd64.go ├── mmap_linux_arm.go ├── mmap_unix.go ├── mmap_unix64.go └── mmap_windows.go /LICENSE: -------------------------------------------------------------------------------- 1 | gommap - Memory mapping support for Go. 2 | 3 | Copyright (c) 2010-2012, Gustavo Niemeyer 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | gommap 3 | ====== 4 | 5 | This is a git mirror of [launchpad.net/gommap][bzr_source]. The `master` branch 6 | includes [this patch][osx_patch], which adds support for darwin64 (MacOS X), as 7 | well as [Windows support by @matt-farmer][windows_patch]. 8 | 9 | [bzr_source]: http://launchpad.net/gommap 10 | [osx_patch]: https://code.launchpad.net/~karl-excors/gommap/gommap-darwin64/+merge/129364 11 | [windows_patch]: https://github.com/tysonmote/gommap/pull/3 12 | 13 | [Read more](http://labix.org/gommap) 14 | 15 | [API documentation](https://pkg.go.dev/github.com/tysonmote/gommap) 16 | 17 | Notes on Windows Support by @matt-farmer 18 | ======================================== 19 | 20 | Gommap is a dependency within the [liftbridge](https://github.com/liftbridge-io/liftbridge) streaming engine which we are using, and we need to be able to run the streaming server on windows. 21 | 22 | The Gommap windows implementation was done by Qing Miao from the NSIP team, but we drew heavily on work from others which are referenced below to make sure they get the credit they deserve - it's also ended up being a bit of a sample of known golang memory maps that work cross-platform, so putting it here in case others find it useful. 23 | 24 | The state of many implementations of the mem-maps is unclear in terms of support, how active the repos are etc. Of course this is a low-level piece of any system so probably once people have one working they don't do any changes until an OS or processor architecture change forces the need. 25 | 26 | We created this fork really because we're prepared to support the implementation for now, and we're passing the changes back up to the original repo so they can accept them if they want to. 27 | 28 | The windows version is designed to require no changes to existing code; certainly this is the case for our liftbridge requirement, the liftbridge server now builds and runs fine on windows, and it's been subjected to high volume tests that would force the flushing of the map without incident. 29 | 30 | ### Limitations 31 | 1. We have only tested this on 64-bit Windows 32 | 1. We have not been able to implement the (mmap)Advise() or (mmap)IsResident() functions - later versions of windows may have apis that can help to support these (from the documentation we've been able to find), but go can only distinguish os and architecture and those 'advanced' features are not generically available to 'windows'. Please raise an issue if this is a show-stopper. 33 | 34 | 35 | ### Prior Art 36 | Here’s a list of the alternative go mem-map packages that we looked at for help and/or borrowed from directly, and which anyone else may find helpful if you need cross-platform memory-mapping in go: 37 | 38 | https://github.com/edsrzf/mmap-go 39 | This one has a very similar interface to the original labix library, but does seem to provide support for windows and various linux distros and Mac. 40 | 41 | https://github.com/golang/exp/tree/master/mmap 42 | Package from the golang devs for mmap. Not immediately useful for our requirement as it only offers a reader interface, but does have nice use of the syscall package in the windows golang file that might be useful if we end up having to create our own library completely from scratch. 43 | 44 | https://github.com/justmao945/tama/tree/master/mmap 45 | This one uses a full go File paradigm rather than a byte array. 46 | 47 | https://github.com/influxdata/platform/tree/master/pkg/mmap 48 | This one is used by influxdb, so we know it works on multiple platforms (we also use influxdb as part of the same project). Difference here is that the API is much simpler, just open - returns a byte array, and then close! 49 | 50 | https://github.com/go-util/mmap 51 | An interesting one that is actively developed. Uses a mixture of file/byte array paradigms, so may operate on Windows using a file-based approach, with lower-level calls for unix systems; offers reader and writer interfaces if required. 52 | 53 | https://github.com/AlexStocks/goext/tree/master/syscall 54 | Another active repo, with a mmap for unix and windows, offers the simple interface for byte array which should be compatible with the simple calls used by liftbridge. 55 | 56 | 57 | -------------------------------------------------------------------------------- /consts.c.txt: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define pcomment(COMMENT) printf("// %s\n", COMMENT) 5 | #define ppackage(NAME) printf("package %s\n", NAME) 6 | #define ptype(NAME, TYPE) printf("\ntype %s %s\n", #NAME, #TYPE) 7 | #define pconstblock(EXPS) { printf("\nconst (\n"); {EXPS} printf(")\n"); } 8 | #define pconst(NAME, TYPE) printf("\t%-15s %s = 0x%x\n", #NAME, #TYPE, NAME) 9 | 10 | int main(int argc, char *argvc[]) { 11 | pcomment("** This file is automatically generated from consts.c.txt **\n"); 12 | pcomment("+build !windows\n"); 13 | ppackage("gommap"); 14 | ptype(ProtFlags, uint); 15 | pconstblock( 16 | pconst(PROT_NONE, ProtFlags); 17 | pconst(PROT_READ, ProtFlags); 18 | pconst(PROT_WRITE, ProtFlags); 19 | pconst(PROT_EXEC, ProtFlags); 20 | ) 21 | ptype(MapFlags, uint); 22 | pconstblock( 23 | pconst(MAP_SHARED, MapFlags); 24 | pconst(MAP_PRIVATE, MapFlags); 25 | pconst(MAP_FIXED, MapFlags); 26 | pconst(MAP_ANONYMOUS, MapFlags); 27 | pconst(MAP_GROWSDOWN, MapFlags); 28 | pconst(MAP_LOCKED, MapFlags); 29 | pconst(MAP_NONBLOCK, MapFlags); 30 | pconst(MAP_NORESERVE, MapFlags); 31 | pconst(MAP_POPULATE, MapFlags); 32 | ) 33 | ptype(SyncFlags, uint); 34 | pconstblock( 35 | pconst(MS_SYNC, SyncFlags); 36 | pconst(MS_ASYNC, SyncFlags); 37 | pconst(MS_INVALIDATE, SyncFlags); 38 | ) 39 | ptype(AdviseFlags, uint); 40 | pconstblock( 41 | pconst(MADV_NORMAL, AdviseFlags); 42 | pconst(MADV_RANDOM, AdviseFlags); 43 | pconst(MADV_SEQUENTIAL, AdviseFlags); 44 | pconst(MADV_WILLNEED, AdviseFlags); 45 | pconst(MADV_DONTNEED, AdviseFlags); 46 | pconst(MADV_REMOVE, AdviseFlags); 47 | pconst(MADV_DONTFORK, AdviseFlags); 48 | pconst(MADV_DOFORK, AdviseFlags); 49 | ) 50 | return 0; 51 | } 52 | 53 | /* vim:ft=c 54 | */ 55 | -------------------------------------------------------------------------------- /consts.go: -------------------------------------------------------------------------------- 1 | // ** This file is automatically generated from consts.c.txt ** 2 | // +build !windows 3 | 4 | package gommap 5 | 6 | type ProtFlags uint 7 | 8 | const ( 9 | PROT_NONE ProtFlags = 0x0 10 | PROT_READ ProtFlags = 0x1 11 | PROT_WRITE ProtFlags = 0x2 12 | PROT_EXEC ProtFlags = 0x4 13 | ) 14 | 15 | type MapFlags uint 16 | 17 | const ( 18 | MAP_SHARED MapFlags = 0x1 19 | MAP_PRIVATE MapFlags = 0x2 20 | MAP_FIXED MapFlags = 0x10 21 | MAP_ANONYMOUS MapFlags = 0x20 22 | MAP_GROWSDOWN MapFlags = 0x100 23 | MAP_LOCKED MapFlags = 0x2000 24 | MAP_NONBLOCK MapFlags = 0x10000 25 | MAP_NORESERVE MapFlags = 0x4000 26 | MAP_POPULATE MapFlags = 0x8000 27 | ) 28 | 29 | type SyncFlags uint 30 | 31 | const ( 32 | MS_SYNC SyncFlags = 0x4 33 | MS_ASYNC SyncFlags = 0x1 34 | MS_INVALIDATE SyncFlags = 0x2 35 | ) 36 | 37 | type AdviseFlags uint 38 | 39 | const ( 40 | MADV_NORMAL AdviseFlags = 0x0 41 | MADV_RANDOM AdviseFlags = 0x1 42 | MADV_SEQUENTIAL AdviseFlags = 0x2 43 | MADV_WILLNEED AdviseFlags = 0x3 44 | MADV_DONTNEED AdviseFlags = 0x4 45 | MADV_REMOVE AdviseFlags = 0x9 46 | MADV_DONTFORK AdviseFlags = 0xa 47 | MADV_DOFORK AdviseFlags = 0xb 48 | ) 49 | -------------------------------------------------------------------------------- /consts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gcc -xc -Wall -pedantic consts.c.txt -o _consts.out 3 | ./_consts.out > consts.go 4 | rm ./_consts.out 5 | -------------------------------------------------------------------------------- /consts_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package gommap 4 | 5 | const ( 6 | // RDONLY maps the memory read-only. 7 | // Attempts to write to the MMap object will result in undefined behavior. 8 | RDONLY = 0 9 | // RDWR maps the memory as read-write. Writes to the MMap object will update the 10 | // underlying file. 11 | RDWR = 1 << iota 12 | // COPY maps the memory as copy-on-write. Writes to the MMap object will affect 13 | // memory, but the underlying file will remain unchanged. 14 | COPY 15 | // If EXEC is set, the mapped memory is marked as executable. 16 | EXEC 17 | ) 18 | 19 | type ProtFlags uint 20 | 21 | const ( 22 | // PROT_NONE ProtFlags = 0x0 23 | // PROT_READ ProtFlags = 0x1 24 | // PROT_WRITE ProtFlags = 0x2 25 | // PROT_EXEC ProtFlags = 0x4 26 | 27 | // PROT_NONE ProtFlags = 0x0 /* No PROT_NONE on windows */ 28 | PROT_READ ProtFlags = RDONLY 29 | PROT_WRITE ProtFlags = RDWR 30 | PROT_COPY ProtFlags = COPY /* PROT_COPY Only on windows, use this flag to implement MAP_PRIVATE */ 31 | PROT_EXEC ProtFlags = EXEC 32 | ) 33 | 34 | /* on win, use ProtFlags to simulate MapFlags */ 35 | 36 | type MapFlags uint 37 | 38 | const ( 39 | MAP_SHARED MapFlags = 0x1 40 | MAP_PRIVATE MapFlags = 0x2 41 | MAP_FIXED MapFlags = 0x10 42 | MAP_ANONYMOUS MapFlags = 0x20 43 | MAP_GROWSDOWN MapFlags = 0x100 44 | MAP_LOCKED MapFlags = 0x2000 45 | MAP_NONBLOCK MapFlags = 0x10000 46 | MAP_NORESERVE MapFlags = 0x4000 47 | MAP_POPULATE MapFlags = 0x8000 48 | ) 49 | 50 | type SyncFlags uint 51 | 52 | const ( 53 | MS_SYNC SyncFlags = 0x4 54 | MS_ASYNC SyncFlags = 0x1 55 | MS_INVALIDATE SyncFlags = 0x2 56 | ) 57 | 58 | type AdviseFlags uint 59 | 60 | const ( 61 | MADV_NORMAL AdviseFlags = 0x0 62 | MADV_RANDOM AdviseFlags = 0x1 63 | MADV_SEQUENTIAL AdviseFlags = 0x2 64 | MADV_WILLNEED AdviseFlags = 0x3 65 | MADV_DONTNEED AdviseFlags = 0x4 66 | MADV_REMOVE AdviseFlags = 0x9 67 | MADV_DONTFORK AdviseFlags = 0xa 68 | MADV_DOFORK AdviseFlags = 0xb 69 | ) 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tysonmote/gommap 2 | 3 | go 1.17 4 | 5 | require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 6 | 7 | require ( 8 | github.com/kr/pretty v0.2.1 // indirect 9 | github.com/kr/text v0.1.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 2 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 3 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 4 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 7 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 8 | -------------------------------------------------------------------------------- /gommap.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | // This package offers the MMap type that manipulates a memory mapped file or 4 | // device. 5 | // 6 | // IMPORTANT NOTE (1): The MMap type is backed by an unsafe memory region, 7 | // which is not covered by the normal rules of Go's memory management. If a 8 | // slice is taken out of it, and then the memory is explicitly unmapped through 9 | // one of the available methods, both the MMap value itself and the slice 10 | // obtained will now silently point to invalid memory. Attempting to access 11 | // data in them will crash the application. 12 | package gommap 13 | 14 | import ( 15 | "os" 16 | "reflect" 17 | "syscall" 18 | "unsafe" 19 | ) 20 | 21 | // The MMap type represents a memory mapped file or device. The slice offers 22 | // direct access to the memory mapped content. 23 | // 24 | // IMPORTANT: Please see note in the package documentation regarding the way 25 | // in which this type behaves. 26 | type MMap []byte 27 | 28 | // Map creates a new mapping in the virtual address space of the calling process. 29 | // This function will attempt to map the entire file by using the fstat system 30 | // call with the provided file descriptor to discover its length. 31 | func Map(fd uintptr, prot ProtFlags, flags MapFlags) (MMap, error) { 32 | mmap, err := MapAt(0, fd, 0, -1, prot, flags) 33 | return mmap, err 34 | } 35 | 36 | // MapRegion creates a new mapping in the virtual address space of the calling 37 | // process, using the specified region of the provided file or device. If -1 is 38 | // provided as length, this function will attempt to map until the end of the 39 | // provided file descriptor by using the fstat system call to discover its 40 | // length. 41 | func MapRegion(fd uintptr, offset, length int64, prot ProtFlags, flags MapFlags) (MMap, error) { 42 | mmap, err := MapAt(0, fd, offset, length, prot, flags) 43 | return mmap, err 44 | } 45 | 46 | // MapAt creates a new mapping in the virtual address space of the calling 47 | // process, using the specified region of the provided file or device. The 48 | // provided addr parameter will be used as a hint of the address where the 49 | // kernel should position the memory mapped region. If -1 is provided as 50 | // length, this function will attempt to map until the end of the provided 51 | // file descriptor by using the fstat system call to discover its length. 52 | func MapAt(addr uintptr, fd uintptr, offset, length int64, prot ProtFlags, flags MapFlags) (MMap, error) { 53 | if length == -1 { 54 | var stat syscall.Stat_t 55 | if err := syscall.Fstat(int(fd), &stat); err != nil { 56 | return nil, err 57 | } 58 | length = stat.Size 59 | } 60 | addr, err := mmap_syscall(addr, uintptr(length), uintptr(prot), uintptr(flags), fd, offset) 61 | if err != syscall.Errno(0) { 62 | return nil, err 63 | } 64 | mmap := MMap{} 65 | 66 | dh := (*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 67 | dh.Data = addr 68 | dh.Len = int(length) // Hmmm.. truncating here feels like trouble. 69 | dh.Cap = dh.Len 70 | return mmap, nil 71 | } 72 | 73 | // UnsafeUnmap deletes the memory mapped region defined by the mmap slice. This 74 | // will also flush any remaining changes, if necessary. Using mmap or any 75 | // other slices based on it after this method has been called will crash the 76 | // application. 77 | func (mmap MMap) UnsafeUnmap() error { 78 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 79 | _, _, err := syscall.Syscall(syscall.SYS_MUNMAP, uintptr(rh.Data), uintptr(rh.Len), 0) 80 | if err != 0 { 81 | return err 82 | } 83 | return nil 84 | } 85 | 86 | // Sync flushes changes made to the region determined by the mmap slice 87 | // back to the device. Without calling this method, there are no guarantees 88 | // that changes will be flushed back before the region is unmapped. The 89 | // flags parameter specifies whether flushing should be done synchronously 90 | // (before the method returns) with MS_SYNC, or asynchronously (flushing is just 91 | // scheduled) with MS_ASYNC. 92 | func (mmap MMap) Sync(flags SyncFlags) error { 93 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 94 | _, _, err := syscall.Syscall(syscall.SYS_MSYNC, uintptr(rh.Data), uintptr(rh.Len), uintptr(flags)) 95 | if err != 0 { 96 | return err 97 | } 98 | return nil 99 | } 100 | 101 | // Advise advises the kernel about how to handle the mapped memory 102 | // region in terms of input/output paging within the memory region 103 | // defined by the mmap slice. 104 | func (mmap MMap) Advise(advice AdviseFlags) error { 105 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 106 | _, _, err := syscall.Syscall(syscall.SYS_MADVISE, uintptr(rh.Data), uintptr(rh.Len), uintptr(advice)) 107 | if err != 0 { 108 | return err 109 | } 110 | return nil 111 | } 112 | 113 | // Protect changes the protection flags for the memory mapped region 114 | // defined by the mmap slice. 115 | func (mmap MMap) Protect(prot ProtFlags) error { 116 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 117 | _, _, err := syscall.Syscall(syscall.SYS_MPROTECT, uintptr(rh.Data), uintptr(rh.Len), uintptr(prot)) 118 | if err != 0 { 119 | return err 120 | } 121 | return nil 122 | } 123 | 124 | // Lock locks the mapped region defined by the mmap slice, 125 | // preventing it from being swapped out. 126 | func (mmap MMap) Lock() error { 127 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 128 | _, _, err := syscall.Syscall(syscall.SYS_MLOCK, uintptr(rh.Data), uintptr(rh.Len), 0) 129 | if err != 0 { 130 | return err 131 | } 132 | return nil 133 | } 134 | 135 | // Unlock unlocks the mapped region defined by the mmap slice, 136 | // allowing it to swap out again. 137 | func (mmap MMap) Unlock() error { 138 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 139 | _, _, err := syscall.Syscall(syscall.SYS_MUNLOCK, uintptr(rh.Data), uintptr(rh.Len), 0) 140 | if err != 0 { 141 | return err 142 | } 143 | return nil 144 | } 145 | 146 | // IsResident returns a slice of booleans informing whether the respective 147 | // memory page in mmap was mapped at the time the call was made. 148 | func (mmap MMap) IsResident() ([]bool, error) { 149 | pageSize := os.Getpagesize() 150 | result := make([]bool, (len(mmap)+pageSize-1)/pageSize) 151 | rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 152 | resulth := *(*reflect.SliceHeader)(unsafe.Pointer(&result)) 153 | _, _, err := syscall.Syscall(syscall.SYS_MINCORE, uintptr(rh.Data), uintptr(rh.Len), uintptr(resulth.Data)) 154 | for i := range result { 155 | *(*uint8)(unsafe.Pointer(&result[i])) &= 1 156 | } 157 | if err != 0 { 158 | return nil, err 159 | } 160 | return result, nil 161 | } 162 | -------------------------------------------------------------------------------- /gommap_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package gommap 4 | 5 | import ( 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "syscall" 10 | "testing" 11 | 12 | . "gopkg.in/check.v1" 13 | ) 14 | 15 | func TestAll(t *testing.T) { 16 | TestingT(t) 17 | } 18 | 19 | type S struct { 20 | file *os.File 21 | } 22 | 23 | var _ = Suite(&S{}) 24 | 25 | var testData = []byte("0123456789ABCDEF") 26 | 27 | func (s *S) SetUpTest(c *C) { 28 | testPath := path.Join(c.MkDir(), "test.txt") 29 | file, err := os.Create(testPath) 30 | if err != nil { 31 | panic(err.Error()) 32 | } 33 | s.file = file 34 | s.file.Write(testData) 35 | } 36 | 37 | func (s *S) TearDownTest(c *C) { 38 | s.file.Close() 39 | } 40 | 41 | func (s *S) TestUnsafeUnmap(c *C) { 42 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 43 | c.Assert(err, IsNil) 44 | c.Assert(mmap.UnsafeUnmap(), IsNil) 45 | } 46 | 47 | func (s *S) TestReadWrite(c *C) { 48 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 49 | c.Assert(err, IsNil) 50 | defer mmap.UnsafeUnmap() 51 | c.Assert([]byte(mmap), DeepEquals, testData) 52 | 53 | mmap[9] = 'X' 54 | mmap.Sync(MS_SYNC) 55 | 56 | fileData, err := ioutil.ReadFile(s.file.Name()) 57 | c.Assert(err, IsNil) 58 | c.Assert(fileData, DeepEquals, []byte("012345678XABCDEF")) 59 | } 60 | 61 | func (s *S) TestSliceMethods(c *C) { 62 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 63 | c.Assert(err, IsNil) 64 | defer mmap.UnsafeUnmap() 65 | c.Assert([]byte(mmap), DeepEquals, testData) 66 | 67 | mmap[9] = 'X' 68 | mmap[7:10].Sync(MS_SYNC) 69 | 70 | fileData, err := ioutil.ReadFile(s.file.Name()) 71 | c.Assert(err, IsNil) 72 | c.Assert(fileData, DeepEquals, []byte("012345678XABCDEF")) 73 | } 74 | 75 | func (s *S) TestProtFlagsAndErr(c *C) { 76 | testPath := s.file.Name() 77 | s.file.Close() 78 | file, err := os.Open(testPath) 79 | c.Assert(err, IsNil) 80 | s.file = file 81 | _, err = Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 82 | // For this to happen, both the error and the protection flag must work. 83 | c.Assert(err, Equals, syscall.EACCES) 84 | } 85 | 86 | func (s *S) TestFlags(c *C) { 87 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 88 | c.Assert(err, IsNil) 89 | defer mmap.UnsafeUnmap() 90 | 91 | mmap[9] = 'X' 92 | mmap.Sync(MS_SYNC) 93 | 94 | fileData, err := ioutil.ReadFile(s.file.Name()) 95 | c.Assert(err, IsNil) 96 | // Shouldn't have written, since the map is private. 97 | c.Assert(fileData, DeepEquals, []byte("0123456789ABCDEF")) 98 | } 99 | 100 | func (s *S) TestAdvise(c *C) { 101 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 102 | c.Assert(err, IsNil) 103 | defer mmap.UnsafeUnmap() 104 | 105 | // A bit tricky to blackbox-test these. 106 | err = mmap.Advise(MADV_RANDOM) 107 | c.Assert(err, IsNil) 108 | 109 | err = mmap.Advise(9999) 110 | c.Assert(err, ErrorMatches, "invalid argument") 111 | } 112 | 113 | func (s *S) TestProtect(c *C) { 114 | mmap, err := Map(s.file.Fd(), PROT_READ, MAP_SHARED) 115 | c.Assert(err, IsNil) 116 | defer mmap.UnsafeUnmap() 117 | c.Assert([]byte(mmap), DeepEquals, testData) 118 | 119 | err = mmap.Protect(PROT_READ | PROT_WRITE) 120 | c.Assert(err, IsNil) 121 | 122 | // If this operation doesn't blow up tests, the call above worked. 123 | mmap[9] = 'X' 124 | } 125 | 126 | func (s *S) TestLock(c *C) { 127 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 128 | c.Assert(err, IsNil) 129 | defer mmap.UnsafeUnmap() 130 | 131 | // A bit tricky to blackbox-test these. 132 | err = mmap.Lock() 133 | c.Assert(err, IsNil) 134 | 135 | err = mmap.Lock() 136 | c.Assert(err, IsNil) 137 | 138 | err = mmap.Unlock() 139 | c.Assert(err, IsNil) 140 | 141 | err = mmap.Unlock() 142 | c.Assert(err, IsNil) 143 | } 144 | 145 | func (s *S) TestIsResidentUnderOnePage(c *C) { 146 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 147 | c.Assert(err, IsNil) 148 | defer mmap.UnsafeUnmap() 149 | 150 | mapped, err := mmap.IsResident() 151 | c.Assert(err, IsNil) 152 | c.Assert(mapped, DeepEquals, []bool{true}) 153 | } 154 | -------------------------------------------------------------------------------- /gommap_twopage_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!freebsd 2 | 3 | package gommap 4 | 5 | import ( 6 | "os" 7 | "path" 8 | 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | func (s *S) TestIsResidentTwoPages(c *C) { 13 | testPath := path.Join(c.MkDir(), "test.txt") 14 | file, err := os.Create(testPath) 15 | c.Assert(err, IsNil) 16 | defer file.Close() 17 | 18 | file.Seek(int64(os.Getpagesize()*2-1), 0) 19 | file.Write([]byte{'x'}) 20 | 21 | mmap, err := Map(file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 22 | c.Assert(err, IsNil) 23 | defer mmap.UnsafeUnmap() 24 | 25 | // Not entirely a stable test, but should usually work. 26 | 27 | mmap[len(mmap)-1] = 'x' 28 | 29 | mapped, err := mmap.IsResident() 30 | c.Assert(err, IsNil) 31 | c.Assert(mapped, DeepEquals, []bool{false, true}) 32 | 33 | mmap[0] = 'x' 34 | 35 | mapped, err = mmap.IsResident() 36 | c.Assert(err, IsNil) 37 | c.Assert(mapped, DeepEquals, []bool{true, true}) 38 | } 39 | -------------------------------------------------------------------------------- /gommap_windows.go: -------------------------------------------------------------------------------- 1 | // This package offers the MMap type that manipulates a memory mapped file or 2 | // device. 3 | // 4 | // IMPORTANT NOTE (1): The MMap type is backed by an unsafe memory region, 5 | // which is not covered by the normal rules of Go's memory management. If a 6 | // slice is taken out of it, and then the memory is explicitly unmapped through 7 | // one of the available methods, both the MMap value itself and the slice 8 | // obtained will now silently point to invalid memory. Attempting to access 9 | // data in them will crash the application. 10 | 11 | // +build windows 12 | 13 | package gommap 14 | 15 | import ( 16 | "errors" 17 | "os" 18 | "reflect" 19 | "syscall" 20 | "unsafe" 21 | ) 22 | 23 | // The MMap type represents a memory mapped file or device. The slice offers 24 | // direct access to the memory mapped content. 25 | // 26 | // IMPORTANT: Please see note in the package documentation regarding the way 27 | // in which this type behaves. 28 | type MMap []byte 29 | 30 | // In order to implement 'Protect', use this to get back the original MMap properties from the memory address. 31 | var mmapAttrs = map[uintptr]*struct { 32 | fd uintptr 33 | offset int64 34 | length int64 35 | prot ProtFlags 36 | flags MapFlags 37 | }{} 38 | 39 | // GetFileSize gets the file length from its fd 40 | func GetFileSize(fd uintptr) (int64, error) { 41 | fh := syscall.Handle(fd) 42 | fsize, err := syscall.Seek(syscall.Handle(fh), 0, 2) 43 | syscall.Seek(fh, 0, 0) 44 | return fsize, err 45 | } 46 | 47 | // Map creates a new mapping in the virtual address space of the calling process. 48 | // This function will attempt to map the entire file by using the fstat system 49 | // call with the provided file descriptor to discover its length. 50 | func Map(fd uintptr, prot ProtFlags, flags MapFlags) (MMap, error) { 51 | return MapRegion(fd, 0, -1, prot, flags) 52 | } 53 | 54 | // MapRegion creates a new mapping in the virtual address space of the calling 55 | // process, using the specified region of the provided file or device. If -1 is 56 | // provided as length, this function will attempt to map until the end of the 57 | // provided file descriptor by using the fstat system call to discover its 58 | // length. 59 | func MapRegion(fd uintptr, offset, length int64, prot ProtFlags, flags MapFlags) (MMap, error) { 60 | if offset%int64(os.Getpagesize()) != 0 { 61 | return nil, errors.New("offset parameter must be a multiple of the system's page size") 62 | } 63 | if length == -1 { 64 | length, _ = GetFileSize(fd) 65 | } 66 | /* on windows, use PROT_COPY to do the same thing as linux MAP_PRIVATE flag do */ 67 | if flags == MAP_PRIVATE { 68 | prot = PROT_COPY 69 | } 70 | // return mmap(length, uintptr(prot), uintptr(flags), fd, offset) 71 | 72 | /*******************************/ 73 | m, e := mmap(length, uintptr(prot), uintptr(flags), fd, offset) 74 | dh := (*reflect.SliceHeader)(unsafe.Pointer(&m)) 75 | mmapAttrs[dh.Data] = &struct { 76 | fd uintptr 77 | offset int64 78 | length int64 79 | prot ProtFlags 80 | flags MapFlags 81 | }{fd, offset, length, prot, flags} 82 | return m, e 83 | } 84 | 85 | func (mmap *MMap) header() *reflect.SliceHeader { 86 | return (*reflect.SliceHeader)(unsafe.Pointer(mmap)) 87 | } 88 | 89 | // UnsafeUnmap deletes the memory mapped region defined by the mmap slice. This 90 | // will also flush any remaining changes, if necessary. Using mmap or any 91 | // other slices based on it after this method has been called will crash the 92 | // application. 93 | func (mmap MMap) UnsafeUnmap() error { 94 | dh := mmap.header() 95 | return unmap(dh.Data, uintptr(dh.Len)) 96 | } 97 | 98 | // Sync flushes changes made to the region determined by the mmap slice 99 | // back to the device. Without calling this method, there are no guarantees 100 | // that changes will be flushed back before the region is unmapped. The 101 | // flags parameter specifies whether flushing should be done synchronously 102 | // (before the method returns) with MS_SYNC, or asynchronously (flushing is just 103 | // scheduled) with MS_ASYNC. 104 | func (mmap MMap) Sync(flags SyncFlags) error { 105 | dh := mmap.header() 106 | return flush(dh.Data, uintptr(dh.Len)) 107 | } 108 | 109 | // // Advise advises the kernel about how to handle the mapped memory 110 | // // region in terms of input/output paging within the memory region 111 | // // defined by the mmap slice. 112 | // func (mmap MMap) Advise(advice AdviseFlags) error { 113 | // // rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 114 | // // _, _, err := syscall.Syscall(syscall.SYS_MADVISE, uintptr(rh.Data), uintptr(rh.Len), uintptr(advice)) 115 | // // if err != 0 { 116 | // // return err 117 | // // } 118 | // // return nil 119 | // } 120 | 121 | // Protect changes the protection flags for the memory mapped region 122 | // defined by the mmap slice. 123 | // We use unmap & map again to implement this on windows. So can only change the protect flags on the whole 124 | func (mmap *MMap) Protect(prot ProtFlags) (err error) { 125 | dh := mmap.header() 126 | var m MMap 127 | if err = mmap.UnsafeUnmap(); err == nil { 128 | fd, offset, length, flags := mmapAttrs[dh.Data].fd, mmapAttrs[dh.Data].offset, mmapAttrs[dh.Data].length, mmapAttrs[dh.Data].flags 129 | mmapAttrs[dh.Data] = nil 130 | if m, err = MapRegion(fd, offset, length, prot, flags); err == nil { 131 | mmap = &m 132 | } 133 | } 134 | return 135 | } 136 | 137 | // Lock locks the mapped region defined by the mmap slice, 138 | // preventing it from being swapped out. 139 | func (mmap MMap) Lock() error { 140 | dh := mmap.header() 141 | return lock(dh.Data, uintptr(dh.Len)) 142 | } 143 | 144 | // Unlock unlocks the mapped region defined by the mmap slice, 145 | // allowing it to swap out again. 146 | func (mmap MMap) Unlock() error { 147 | dh := mmap.header() 148 | return unlock(dh.Data, uintptr(dh.Len)) 149 | } 150 | 151 | // // IsResident returns a slice of booleans informing whether the respective 152 | // // memory page in mmap was mapped at the time the call was made. 153 | // func (mmap MMap) IsResident() ([]bool, error) { 154 | // pageSize := os.Getpagesize() 155 | // result := make([]bool, (len(mmap)+pageSize-1)/pageSize) 156 | // rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap)) 157 | // resulth := *(*reflect.SliceHeader)(unsafe.Pointer(&result)) 158 | // _, _, err := syscall.Syscall(syscall.SYS_MINCORE, uintptr(rh.Data), uintptr(rh.Len), uintptr(resulth.Data)) 159 | // for i := range result { 160 | // *(*uint8)(unsafe.Pointer(&result[i])) &= 1 161 | // } 162 | // if err != 0 { 163 | // return nil, err 164 | // } 165 | // return result, nil 166 | // } 167 | -------------------------------------------------------------------------------- /gommap_windows_test.go: -------------------------------------------------------------------------------- 1 | package gommap 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "syscall" 8 | "testing" 9 | 10 | . "gopkg.in/check.v1" 11 | ) 12 | 13 | func TestAll(t *testing.T) { 14 | TestingT(t) 15 | } 16 | 17 | type S struct { 18 | file *os.File 19 | } 20 | 21 | var _ = Suite(&S{}) 22 | 23 | var testData = []byte("0123456789ABCDEF") 24 | 25 | func (s *S) SetUpTest(c *C) { 26 | testPath := path.Join(c.MkDir(), "test.txt") 27 | file, err := os.Create(testPath) 28 | if err != nil { 29 | panic(err.Error()) 30 | } 31 | s.file = file 32 | s.file.Write(testData) 33 | } 34 | 35 | func (s *S) TearDownTest(c *C) { 36 | s.file.Close() 37 | } 38 | 39 | func (s *S) TestUnsafeUnmap(c *C) { 40 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 41 | c.Assert(err, IsNil) 42 | c.Assert(mmap.UnsafeUnmap(), IsNil) 43 | } 44 | 45 | func (s *S) TestReadWrite(c *C) { 46 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 47 | c.Assert(err, IsNil) 48 | defer mmap.UnsafeUnmap() 49 | c.Assert([]byte(mmap), DeepEquals, testData) 50 | 51 | mmap[9] = 'X' 52 | mmap.Sync(MS_SYNC) 53 | 54 | fileData, err := ioutil.ReadFile(s.file.Name()) 55 | c.Assert(err, IsNil) 56 | c.Assert(fileData, DeepEquals, []byte("012345678XABCDEF")) 57 | } 58 | 59 | func (s *S) TestSliceMethods(c *C) { 60 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 61 | c.Assert(err, IsNil) 62 | defer mmap.UnsafeUnmap() 63 | c.Assert([]byte(mmap), DeepEquals, testData) 64 | 65 | mmap[9] = 'X' 66 | mmap[7:10].Sync(MS_SYNC) 67 | 68 | fileData, err := ioutil.ReadFile(s.file.Name()) 69 | c.Assert(err, IsNil) 70 | c.Assert(fileData, DeepEquals, []byte("012345678XABCDEF")) 71 | } 72 | 73 | func (s *S) TestProtFlagsAndErr(c *C) { 74 | testPath := s.file.Name() 75 | s.file.Close() 76 | file, err := os.Open(testPath) 77 | c.Assert(err, IsNil) 78 | s.file = file 79 | _, err = Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_SHARED) 80 | // For this to happen, both the error and the protection flag must work. 81 | c.Assert(err, Equals, syscall.EACCES) 82 | } 83 | 84 | func (s *S) TestFlags(c *C) { 85 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 86 | c.Assert(err, IsNil) 87 | defer mmap.UnsafeUnmap() 88 | 89 | mmap[9] = 'X' 90 | mmap.Sync(MS_SYNC) 91 | 92 | fileData, err := ioutil.ReadFile(s.file.Name()) 93 | c.Assert(err, IsNil) 94 | // Shouldn't have written, since the map is private. 95 | c.Assert(fileData, DeepEquals, []byte("0123456789ABCDEF")) 96 | } 97 | 98 | func (s *S) TestProtect(c *C) { 99 | mmap, err := Map(s.file.Fd(), PROT_READ, MAP_SHARED) 100 | c.Assert(err, IsNil) 101 | defer mmap.UnsafeUnmap() 102 | c.Assert([]byte(mmap), DeepEquals, testData) 103 | 104 | err = mmap.Protect(PROT_READ | PROT_WRITE) 105 | c.Assert(err, IsNil) 106 | 107 | // If this operation doesn't blow up tests, the call above worked. 108 | mmap[9] = 'X' 109 | } 110 | 111 | func (s *S) TestLock(c *C) { 112 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 113 | c.Assert(err, IsNil) 114 | defer mmap.UnsafeUnmap() 115 | 116 | // A bit tricky to blackbox-test these. 117 | err = mmap.Lock() 118 | c.Assert(err, IsNil) 119 | 120 | err = mmap.Lock() 121 | c.Assert(err, IsNil) 122 | 123 | err = mmap.Lock() 124 | c.Assert(err, IsNil) 125 | 126 | err = mmap.Unlock() 127 | c.Assert(err, IsNil) 128 | 129 | err = mmap.Unlock() 130 | c.Assert(err, IsNil) 131 | } 132 | 133 | func (s *S) TestSync(c *C) { 134 | mmap, err := Map(s.file.Fd(), PROT_READ|PROT_WRITE, MAP_PRIVATE) 135 | c.Assert(err, IsNil) 136 | defer mmap.UnsafeUnmap() 137 | 138 | err = mmap.Sync(MS_SYNC) 139 | c.Assert(err, IsNil) 140 | } 141 | -------------------------------------------------------------------------------- /mmap_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | package gommap 2 | 3 | import "syscall" 4 | 5 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 6 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP, addr, length, prot, flags, fd, uintptr(offset)) 7 | return addr, err 8 | } 9 | -------------------------------------------------------------------------------- /mmap_darwin_arm64.go: -------------------------------------------------------------------------------- 1 | package gommap 2 | 3 | import "syscall" 4 | 5 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 6 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP, addr, length, prot, flags, fd, uintptr(offset)) 7 | return addr, err 8 | } 9 | -------------------------------------------------------------------------------- /mmap_freebsd_amd64.go: -------------------------------------------------------------------------------- 1 | package gommap 2 | 3 | import "syscall" 4 | 5 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 6 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP, addr, length, prot, flags, fd, uintptr(offset)) 7 | return addr, err 8 | } 9 | -------------------------------------------------------------------------------- /mmap_linux_arm.go: -------------------------------------------------------------------------------- 1 | package gommap 2 | 3 | import "syscall" 4 | 5 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 6 | page := uintptr(offset / 4096) 7 | if offset != int64(page)*4096 { 8 | return 0, syscall.EINVAL 9 | } 10 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP2, addr, length, prot, flags, fd, page) 11 | return addr, err 12 | } 13 | -------------------------------------------------------------------------------- /mmap_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd 2 | // +build 386 3 | 4 | package gommap 5 | 6 | import "syscall" 7 | 8 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 9 | page := uintptr(offset / 4096) 10 | if offset != int64(page)*4096 { 11 | return 0, syscall.EINVAL 12 | } 13 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP2, addr, length, prot, flags, fd, page) 14 | return addr, err 15 | } 16 | -------------------------------------------------------------------------------- /mmap_unix64.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd 2 | // +build amd64 arm64 3 | 4 | package gommap 5 | 6 | import "syscall" 7 | 8 | func mmap_syscall(addr, length, prot, flags, fd uintptr, offset int64) (uintptr, error) { 9 | addr, _, err := syscall.Syscall6(syscall.SYS_MMAP, addr, length, prot, flags, fd, uintptr(offset)) 10 | return addr, err 11 | } 12 | -------------------------------------------------------------------------------- /mmap_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Evan Shaw. 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 windows 6 | 7 | package gommap 8 | 9 | import ( 10 | "errors" 11 | "os" 12 | "sync" 13 | "syscall" 14 | ) 15 | 16 | // mmap on Windows is a two-step process. 17 | // First, we call CreateFileMapping to get a handle. 18 | // Then, we call MapviewToFile to get an actual pointer into memory. 19 | // Because we want to emulate a POSIX-style mmap, we don't want to expose 20 | // the handle -- only the pointer. We also want to return only a byte slice, 21 | // not a struct, so it's convenient to manipulate. 22 | 23 | // We keep this map so that we can get back the original handle from the memory address. 24 | var handleLock sync.Mutex 25 | var handleMap = map[uintptr]syscall.Handle{} 26 | var fileHandleMap = map[uintptr]syscall.Handle{} 27 | var addrLocked = map[uintptr]bool{} 28 | 29 | func mmap(len int64, prot, flags, hfile uintptr, off int64) ([]byte, error) { 30 | flProtect := uint32(syscall.PAGE_READONLY) 31 | dwDesiredAccess := uint32(syscall.FILE_MAP_READ) 32 | switch { 33 | case prot© != 0: 34 | flProtect = syscall.PAGE_WRITECOPY 35 | dwDesiredAccess = syscall.FILE_MAP_COPY 36 | case prot&RDWR != 0: 37 | flProtect = syscall.PAGE_READWRITE 38 | dwDesiredAccess = syscall.FILE_MAP_WRITE 39 | } 40 | if prot&EXEC != 0 { 41 | flProtect <<= 4 42 | dwDesiredAccess |= syscall.FILE_MAP_EXECUTE 43 | } 44 | 45 | // The maximum size is the area of the file, starting from 0, 46 | // that we wish to allow to be mappable. It is the sum of 47 | // the length the user requested, plus the offset where that length 48 | // is starting from. This does not map the data into memory. 49 | maxSizeHigh := uint32((off + len) >> 32) 50 | maxSizeLow := uint32((off + len) & 0xFFFFFFFF) 51 | // TODO: Do we need to set some security attributes? It might help portability. 52 | fileHandle := syscall.Handle(hfile) 53 | h, errno := syscall.CreateFileMapping(fileHandle, nil, flProtect, maxSizeHigh, maxSizeLow, nil) 54 | if h == 0 { 55 | if errno == syscall.ERROR_ACCESS_DENIED { 56 | return nil, syscall.EACCES 57 | } 58 | return nil, os.NewSyscallError("CreateFileMapping", errno) 59 | } 60 | 61 | // Actually map a view of the data into memory. The view's size 62 | // is the length the user requested. 63 | fileOffsetHigh := uint32(off >> 32) 64 | fileOffsetLow := uint32(off & 0xFFFFFFFF) 65 | addr, errno := syscall.MapViewOfFile(h, dwDesiredAccess, fileOffsetHigh, fileOffsetLow, uintptr(len)) 66 | if addr == 0 { 67 | return nil, os.NewSyscallError("MapViewOfFile", errno) 68 | } 69 | handleLock.Lock() 70 | handleMap[addr] = h 71 | fileHandleMap[addr] = fileHandle 72 | handleLock.Unlock() 73 | 74 | m := MMap{} 75 | dh := m.header() 76 | dh.Data = addr 77 | dh.Len = int(len) 78 | dh.Cap = dh.Len 79 | 80 | return m, nil 81 | } 82 | 83 | func flush(addr, len uintptr) error { 84 | errno := syscall.FlushViewOfFile(addr, len) 85 | if errno != nil { 86 | return os.NewSyscallError("FlushViewOfFile", errno) 87 | } 88 | 89 | handleLock.Lock() 90 | defer handleLock.Unlock() 91 | handle, ok := fileHandleMap[addr] 92 | if !ok { 93 | // should be impossible; we would've errored above 94 | return errors.New("unknown base address") 95 | } 96 | 97 | errno = syscall.FlushFileBuffers(handle) 98 | return os.NewSyscallError("FlushFileBuffers", errno) 99 | } 100 | 101 | func lock(addr, len uintptr) error { 102 | if addrLocked[addr] { 103 | return nil 104 | } 105 | errno := syscall.VirtualLock(addr, len) 106 | if errno == nil { 107 | addrLocked[addr] = true 108 | } 109 | return os.NewSyscallError("VirtualLock", errno) 110 | } 111 | 112 | func unlock(addr, len uintptr) error { 113 | if !addrLocked[addr] { 114 | return nil 115 | } 116 | errno := syscall.VirtualUnlock(addr, len) 117 | if errno == nil { 118 | addrLocked[addr] = false 119 | } 120 | return os.NewSyscallError("VirtualUnlock", errno) 121 | } 122 | 123 | func unmap(addr, len uintptr) error { 124 | flush(addr, len) 125 | // Lock the UnmapViewOfFile along with the handleMap deletion. 126 | // As soon as we unmap the view, the OS is free to give the 127 | // same addr to another new map. We don't want another goroutine 128 | // to insert and remove the same addr into handleMap while 129 | // we're trying to remove our old addr/handle pair. 130 | handleLock.Lock() 131 | defer handleLock.Unlock() 132 | err := syscall.UnmapViewOfFile(addr) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | handle, ok := handleMap[addr] 138 | if !ok { 139 | // should be impossible; we would've errored above 140 | return errors.New("unknown base address") 141 | } 142 | delete(handleMap, addr) 143 | delete(fileHandleMap, addr) 144 | 145 | e := syscall.CloseHandle(syscall.Handle(handle)) 146 | return os.NewSyscallError("CloseHandle", e) 147 | } 148 | --------------------------------------------------------------------------------