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