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