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
5package mmap
6
7import (
8	"errors"
9	"os"
10	"sync"
11	"syscall"
12)
13
14// mmap on Windows is a two-step process.
15// First, we call CreateFileMapping to get a handle.
16// Then, we call MapviewToFile to get an actual pointer into memory.
17// Because we want to emulate a POSIX-style mmap, we don't want to expose
18// the handle -- only the pointer. We also want to return only a byte slice,
19// not a struct, so it's convenient to manipulate.
20
21// We keep this map so that we can get back the original handle from the memory address.
22var handleLock sync.Mutex
23var handleMap = map[uintptr]syscall.Handle{}
24
25func mmap(len int, prot, flags, hfile uintptr, off int64) ([]byte, error) {
26	flProtect := uint32(syscall.PAGE_READONLY)
27	dwDesiredAccess := uint32(syscall.FILE_MAP_READ)
28	switch {
29	case prot&COPY != 0:
30		flProtect = syscall.PAGE_WRITECOPY
31		dwDesiredAccess = syscall.FILE_MAP_COPY
32	case prot&RDWR != 0:
33		flProtect = syscall.PAGE_READWRITE
34		dwDesiredAccess = syscall.FILE_MAP_WRITE
35	}
36	if prot&EXEC != 0 {
37		flProtect <<= 4
38		dwDesiredAccess |= syscall.FILE_MAP_EXECUTE
39	}
40
41	// The maximum size is the area of the file, starting from 0,
42	// that we wish to allow to be mappable. It is the sum of
43	// the length the user requested, plus the offset where that length
44	// is starting from. This does not map the data into memory.
45	maxSizeHigh := uint32((off + int64(len)) >> 32)
46	maxSizeLow := uint32((off + int64(len)) & 0xFFFFFFFF)
47	// TODO: Do we need to set some security attributes? It might help portability.
48	h, errno := syscall.CreateFileMapping(syscall.Handle(hfile), nil, flProtect, maxSizeHigh, maxSizeLow, nil)
49	if h == 0 {
50		return nil, os.NewSyscallError("CreateFileMapping", errno)
51	}
52
53	// Actually map a view of the data into memory. The view's size
54	// is the length the user requested.
55	fileOffsetHigh := uint32(off >> 32)
56	fileOffsetLow := uint32(off & 0xFFFFFFFF)
57	addr, errno := syscall.MapViewOfFile(h, dwDesiredAccess, fileOffsetHigh, fileOffsetLow, uintptr(len))
58	if addr == 0 {
59		return nil, os.NewSyscallError("MapViewOfFile", errno)
60	}
61	handleLock.Lock()
62	handleMap[addr] = h
63	handleLock.Unlock()
64
65	m := MMap{}
66	dh := m.header()
67	dh.Data = addr
68	dh.Len = len
69	dh.Cap = dh.Len
70
71	return m, nil
72}
73
74func flush(addr, len uintptr) error {
75	errno := syscall.FlushViewOfFile(addr, len)
76	if errno != nil {
77		return os.NewSyscallError("FlushViewOfFile", errno)
78	}
79
80	handleLock.Lock()
81	defer handleLock.Unlock()
82	handle, ok := handleMap[addr]
83	if !ok {
84		// should be impossible; we would've errored above
85		return errors.New("unknown base address")
86	}
87
88	errno = syscall.FlushFileBuffers(handle)
89	return os.NewSyscallError("FlushFileBuffers", errno)
90}
91
92func lock(addr, len uintptr) error {
93	errno := syscall.VirtualLock(addr, len)
94	return os.NewSyscallError("VirtualLock", errno)
95}
96
97func unlock(addr, len uintptr) error {
98	errno := syscall.VirtualUnlock(addr, len)
99	return os.NewSyscallError("VirtualUnlock", errno)
100}
101
102func unmap(addr, len uintptr) error {
103	flush(addr, len)
104	// Lock the UnmapViewOfFile along with the handleMap deletion.
105	// As soon as we unmap the view, the OS is free to give the
106	// same addr to another new map. We don't want another goroutine
107	// to insert and remove the same addr into handleMap while
108	// we're trying to remove our old addr/handle pair.
109	handleLock.Lock()
110	defer handleLock.Unlock()
111	err := syscall.UnmapViewOfFile(addr)
112	if err != nil {
113		return err
114	}
115
116	handle, ok := handleMap[addr]
117	if !ok {
118		// should be impossible; we would've errored above
119		return errors.New("unknown base address")
120	}
121	delete(handleMap, addr)
122
123	e := syscall.CloseHandle(syscall.Handle(handle))
124	return os.NewSyscallError("CloseHandle", e)
125}
126