cc24807340aa7af1cd6fec5ae558d686948d3e50
[folly.git] / folly / portability / SysMman.cpp
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <folly/portability/SysMman.h>
18
19 #ifdef _WIN32
20 #include <cassert>
21 #include <folly/Portability.h>
22 #include <folly/portability/Windows.h>
23
24 static bool mmap_to_page_protection(int prot, DWORD& ret, DWORD& acc) {
25   if (prot == PROT_NONE) {
26     ret = PAGE_NOACCESS;
27     acc = 0;
28   } else if (prot == PROT_READ) {
29     ret = PAGE_READONLY;
30     acc = FILE_MAP_READ;
31   } else if (prot == PROT_EXEC) {
32     ret = PAGE_EXECUTE;
33     acc = FILE_MAP_EXECUTE;
34   } else if (prot == (PROT_READ | PROT_EXEC)) {
35     ret = PAGE_EXECUTE_READ;
36     acc = FILE_MAP_READ | FILE_MAP_EXECUTE;
37   } else if (prot == (PROT_READ | PROT_WRITE)) {
38     ret = PAGE_READWRITE;
39     acc = FILE_MAP_READ | FILE_MAP_WRITE;
40   } else if (prot == (PROT_READ | PROT_WRITE | PROT_EXEC)) {
41     ret = PAGE_EXECUTE_READWRITE;
42     acc = FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE;
43   } else {
44     return false;
45   }
46   return true;
47 }
48
49 static size_t alignToAllocationGranularity(size_t s) {
50   static size_t granularity = [] {
51     static SYSTEM_INFO inf;
52     GetSystemInfo(&inf);
53     return inf.dwAllocationGranularity;
54   }();
55   return (s + granularity - 1) / granularity * granularity;
56 }
57
58 extern "C" {
59 int madvise(const void* /* addr */, size_t /* len */, int /* advise */) {
60   // We do nothing at all.
61   // Could probably implement dontneed via VirtualAlloc
62   // with the MEM_RESET and MEM_RESET_UNDO flags.
63   return 0;
64 }
65
66 int mlock(const void* addr, size_t len) {
67   // For some strange reason, it's allowed to
68   // lock a nullptr as long as length is zero.
69   // VirtualLock doesn't allow it, so handle
70   // it specially.
71   if (addr == nullptr && len == 0) {
72     return 0;
73   }
74   if (!VirtualLock((void*)addr, len)) {
75     return -1;
76   }
77   return 0;
78 }
79
80 namespace {
81 constexpr uint32_t kMMapLengthMagic = 0xFACEB00C;
82 struct MemMapDebugTrailer {
83   size_t length;
84   uint32_t magic;
85 };
86 }
87
88 void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t off) {
89   // Make sure it's something we support first.
90
91   // No Anon shared.
92   if ((flags & (MAP_ANONYMOUS | MAP_SHARED)) == (MAP_ANONYMOUS | MAP_SHARED)) {
93     return MAP_FAILED;
94   }
95   // No private copy on write.
96   if ((flags & MAP_PRIVATE) == MAP_PRIVATE && fd != -1) {
97     return MAP_FAILED;
98   }
99   // Map isn't anon, must be file backed.
100   if (!(flags & MAP_ANONYMOUS) && fd == -1) {
101     return MAP_FAILED;
102   }
103
104   DWORD newProt;
105   DWORD accessFlags;
106   if (!mmap_to_page_protection(prot, newProt, accessFlags)) {
107     return MAP_FAILED;
108   }
109
110   void* ret;
111   if (!(flags & MAP_ANONYMOUS) || (flags & MAP_SHARED)) {
112     HANDLE h = INVALID_HANDLE_VALUE;
113     if (!(flags & MAP_ANONYMOUS)) {
114       h = (HANDLE)_get_osfhandle(fd);
115     }
116
117     HANDLE fmh = CreateFileMapping(
118         h,
119         nullptr,
120         newProt,
121         (DWORD)((length >> 32) & 0xFFFFFFFF),
122         (DWORD)(length & 0xFFFFFFFF),
123         nullptr);
124     if (fmh == nullptr) {
125       return MAP_FAILED;
126     }
127     ret = MapViewOfFileEx(
128         fmh,
129         accessFlags,
130         (DWORD)(0), // off_t is only 32-bit :(
131         (DWORD)(off & 0xFFFFFFFF),
132         0,
133         addr);
134     if (ret == nullptr) {
135       ret = MAP_FAILED;
136     }
137     CloseHandle(fmh);
138   } else {
139     auto baseLength = length;
140     if (folly::kIsDebug) {
141       // In debug mode we keep track of the length to make
142       // sure you're only munmapping the entire thing if
143       // we're using VirtualAlloc.
144       length += sizeof(MemMapDebugTrailer);
145     }
146
147     // VirtualAlloc rounds size down to a multiple
148     // of the system allocation granularity :(
149     length = alignToAllocationGranularity(length);
150     ret = VirtualAlloc(addr, length, MEM_COMMIT | MEM_RESERVE, newProt);
151     if (ret == nullptr) {
152       return MAP_FAILED;
153     }
154
155     if (folly::kIsDebug) {
156       auto deb = (MemMapDebugTrailer*)((char*)ret + baseLength);
157       deb->length = baseLength;
158       deb->magic = kMMapLengthMagic;
159     }
160   }
161
162   // TODO: Could technically implement MAP_POPULATE via PrefetchVirtualMemory
163   //       Should also see about implementing MAP_NORESERVE
164   return ret;
165 }
166
167 int mprotect(void* addr, size_t size, int prot) {
168   DWORD newProt;
169   DWORD access;
170   if (!mmap_to_page_protection(prot, newProt, access)) {
171     return -1;
172   }
173
174   DWORD oldProt;
175   BOOL res = VirtualProtect(addr, size, newProt, &oldProt);
176   if (!res) {
177     return -1;
178   }
179   return 0;
180 }
181
182 int munlock(const void* addr, size_t length) {
183   // See comment in mlock
184   if (addr == nullptr && length == 0) {
185     return 0;
186   }
187   if (!VirtualUnlock((void*)addr, length)) {
188     return -1;
189   }
190   return 0;
191 }
192
193 int munmap(void* addr, size_t length) {
194   // Try to unmap it as a file, otherwise VirtualFree.
195   if (!UnmapViewOfFile(addr)) {
196     if (folly::kIsDebug) {
197       // We can't do partial unmapping with Windows, so
198       // assert that we aren't trying to do that if we're
199       // in debug mode.
200       MEMORY_BASIC_INFORMATION inf;
201       VirtualQuery(addr, &inf, sizeof(inf));
202       assert(inf.AllocationBase == addr);
203
204       auto deb = (MemMapDebugTrailer*)((char*)addr + length);
205       assert(deb->length == length);
206       assert(deb->magic == kMMapLengthMagic);
207     }
208     if (!VirtualFree(addr, 0, MEM_RELEASE)) {
209       return -1;
210     }
211     return 0;
212   }
213   return 0;
214 }
215 }
216 #endif