Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | HANDLE fileDesHandle (int fd); |
2 | ||
3 | #define BUFSIZE 512 | |
4 | ||
5 | static HANDLE tempFileDes (void) { | |
6 | /* Based on http://msdn.microsoft.com/en-us/library/aa363875(VS.85).aspx */ | |
7 | HANDLE hTempFile; | |
8 | DWORD dwRetVal; | |
9 | DWORD dwBufSize=BUFSIZE; | |
10 | UINT uRetVal; | |
11 | TCHAR szTempName[BUFSIZE]; | |
12 | TCHAR lpPathBuffer[BUFSIZE]; | |
13 | ||
14 | dwRetVal = GetTempPath(dwBufSize, lpPathBuffer); | |
15 | if (dwRetVal > dwBufSize || (dwRetVal == 0)) | |
16 | die ("GetTempPath(%ld,...) failed with error %ld\n", | |
17 | dwBufSize, GetLastError()); | |
18 | uRetVal = GetTempFileName(lpPathBuffer, TEXT("MLtonTempFile"), 0, szTempName); | |
19 | if (uRetVal == 0) | |
20 | die ("GetTempFileName(\"%s\",...) failed with error %ld\n", | |
21 | lpPathBuffer, GetLastError()); | |
22 | hTempFile = CreateFile((LPTSTR) szTempName, | |
23 | GENERIC_READ | GENERIC_WRITE, | |
24 | 0, | |
25 | NULL, | |
26 | TRUNCATE_EXISTING, | |
27 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, | |
28 | NULL); | |
29 | if (hTempFile == INVALID_HANDLE_VALUE) | |
30 | die ("CreateFile(\"%s\",...) failed with error %ld\n", | |
31 | szTempName, GetLastError()); | |
32 | return hTempFile; | |
33 | } | |
34 | ||
35 | typedef struct { | |
36 | HANDLE handle; | |
37 | } *WriteToDiskData; | |
38 | ||
39 | void GC_diskBack_read (void *data, pointer buf, size_t size) { | |
40 | HANDLE h; | |
41 | DWORD d; | |
42 | DWORD dwBytesRead; | |
43 | ||
44 | h = ((WriteToDiskData)data)->handle; | |
45 | d = SetFilePointer (h, 0, NULL, FILE_BEGIN); | |
46 | if (d == INVALID_SET_FILE_POINTER) | |
47 | die ("SetFilePointer failed with error %ld\n", GetLastError()); | |
48 | unless (ReadFile(h, buf, size, &dwBytesRead, NULL)) | |
49 | die ("ReadFile failed with error %ld\n", GetLastError()); | |
50 | } | |
51 | ||
52 | void GC_diskBack_close (void *data) { | |
53 | HANDLE h; | |
54 | ||
55 | h = ((WriteToDiskData)data)->handle; | |
56 | unless (CloseHandle (h)) | |
57 | die ("CloseHandle failed with error %ld.", GetLastError()); | |
58 | free (data); | |
59 | } | |
60 | ||
61 | void *GC_diskBack_write (pointer buf, size_t size) { | |
62 | HANDLE h; | |
63 | WriteToDiskData d; | |
64 | DWORD dwBytesWritten; | |
65 | ||
66 | h = tempFileDes (); | |
67 | unless (WriteFile (h, buf, size, &dwBytesWritten, NULL)) | |
68 | die ("WriteFile failed with error %ld\n", GetLastError()); | |
69 | d = (WriteToDiskData)(malloc_safe (sizeof(*d))); | |
70 | d->handle = h; | |
71 | return d; | |
72 | } | |
73 | ||
74 | static void displayMaps (void) { | |
75 | MEMORY_BASIC_INFORMATION buf; | |
76 | const char *state = "<unset>"; | |
77 | const char *protect = "<unset>"; | |
78 | uintptr_t address; | |
79 | ||
80 | buf.RegionSize = 0; | |
81 | for (address = 0; | |
82 | address + buf.RegionSize >= address; | |
83 | address += buf.RegionSize) { | |
84 | if (0 == VirtualQuery ((LPCVOID)address, &buf, sizeof (buf))) | |
85 | break; | |
86 | if (0 == buf.RegionSize) | |
87 | break; | |
88 | ||
89 | switch (buf.Protect) { | |
90 | case PAGE_READONLY: | |
91 | protect = "PAGE_READONLY"; | |
92 | break; | |
93 | case PAGE_READWRITE: | |
94 | protect = "PAGE_READWRITE"; | |
95 | break; | |
96 | case PAGE_WRITECOPY: | |
97 | protect = "PAGE_WRITECOPY"; | |
98 | break; | |
99 | case PAGE_EXECUTE: | |
100 | protect = "PAGE_EXECUTE"; | |
101 | break; | |
102 | case PAGE_EXECUTE_READ: | |
103 | protect = "PAGE_EXECUTE_READ"; | |
104 | break; | |
105 | case PAGE_EXECUTE_READWRITE: | |
106 | protect = "PAGE_EXECUTE_READWRITE"; | |
107 | break; | |
108 | case PAGE_EXECUTE_WRITECOPY: | |
109 | protect = "PAGE_EXECUTE_WRITECOPY"; | |
110 | break; | |
111 | case PAGE_GUARD: | |
112 | protect = "PAGE_GUARD"; | |
113 | break; | |
114 | case PAGE_NOACCESS: | |
115 | protect = "PAGE_NOACCESS"; | |
116 | break; | |
117 | case PAGE_NOCACHE: | |
118 | protect = "PAGE_NOCACHE"; | |
119 | break; | |
120 | default: | |
121 | assert (FALSE); | |
122 | } | |
123 | switch (buf.State) { | |
124 | case MEM_COMMIT: | |
125 | state = "MEM_COMMIT"; | |
126 | break; | |
127 | case MEM_FREE: | |
128 | state = "MEM_FREE"; | |
129 | break; | |
130 | case MEM_RESERVE: | |
131 | state = "MEM_RESERVE"; | |
132 | break; | |
133 | default: | |
134 | assert (FALSE); | |
135 | } | |
136 | ||
137 | fprintf(stderr, FMTPTR " %10"PRIuMAX" %s %s\n", | |
138 | (uintptr_t)buf.BaseAddress, (uintmax_t)buf.RegionSize, | |
139 | state, protect); | |
140 | } | |
141 | } | |
142 | ||
143 | void GC_displayMem (void) { | |
144 | #ifdef _WIN64 | |
145 | MEMORYSTATUSEX ms; | |
146 | ms.dwLength = sizeof (MEMORYSTATUSEX); | |
147 | GlobalMemoryStatusEx (&ms); | |
148 | ||
149 | fprintf(stderr, "Total Phys. Mem: %"PRIuMAX"\n" | |
150 | "Avail Phys. Mem: %"PRIuMAX"\n" | |
151 | "Total Page File: %"PRIuMAX"\n" | |
152 | "Avail Page File: %"PRIuMAX"\n" | |
153 | "Total Virtual: %"PRIuMAX"\n" | |
154 | "Avail Virtual: %"PRIuMAX"\n", | |
155 | (uintmax_t)ms.ullTotalPhys, | |
156 | (uintmax_t)ms.ullAvailPhys, | |
157 | (uintmax_t)ms.ullTotalPageFile, | |
158 | (uintmax_t)ms.ullAvailPageFile, | |
159 | (uintmax_t)ms.ullTotalVirtual, | |
160 | (uintmax_t)ms.ullAvailVirtual); | |
161 | #else | |
162 | MEMORYSTATUS ms; | |
163 | ms.dwLength = sizeof (MEMORYSTATUS); | |
164 | GlobalMemoryStatus (&ms); | |
165 | ||
166 | fprintf(stderr, "Total Phys. Mem: %"PRIuMAX"\n" | |
167 | "Avail Phys. Mem: %"PRIuMAX"\n" | |
168 | "Total Page File: %"PRIuMAX"\n" | |
169 | "Avail Page File: %"PRIuMAX"\n" | |
170 | "Total Virtual: %"PRIuMAX"\n" | |
171 | "Avail Virtual: %"PRIuMAX"\n", | |
172 | (uintmax_t)ms.dwTotalPhys, | |
173 | (uintmax_t)ms.dwAvailPhys, | |
174 | (uintmax_t)ms.dwTotalPageFile, | |
175 | (uintmax_t)ms.dwAvailPageFile, | |
176 | (uintmax_t)ms.dwTotalVirtual, | |
177 | (uintmax_t)ms.dwAvailVirtual); | |
178 | #endif | |
179 | displayMaps (); | |
180 | } | |
181 | ||
182 | static HANDLE dupHandle (int fd) { | |
183 | HANDLE dupd; | |
184 | HANDLE raw; | |
185 | ||
186 | raw = fileDesHandle (fd); | |
187 | if (raw == (HANDLE)-1 or raw == 0) { | |
188 | errno = EBADF; | |
189 | return 0; | |
190 | } | |
191 | /* 'Inspired' by http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/creating_a_child_process_with_redirected_input_and_output.asp | |
192 | * It's interesting that you can open files for/from other processes... | |
193 | */ | |
194 | unless (DuplicateHandle ( | |
195 | GetCurrentProcess(), /* source process */ | |
196 | raw, /* source handle */ | |
197 | GetCurrentProcess(), /* target process */ | |
198 | &dupd, /* target handle - valid in target proc */ | |
199 | 0, /* ignored b/c DUPLICATE_SAME_ACCESS used */ | |
200 | TRUE, /* this can be inherited by children */ | |
201 | DUPLICATE_SAME_ACCESS))/* keep the same permissions */ | |
202 | { | |
203 | errno = ENOMEM; | |
204 | return 0; | |
205 | } | |
206 | return dupd; | |
207 | } | |
208 | ||
209 | /* Windows memory is allocated in two phases: reserve and commit. | |
210 | * A reservation makes the address space unavailable to other reservations. | |
211 | * Commiting reserved memory actually maps the reserved memory for use. | |
212 | * Decommitting a portion of a reservation releases the physical memory only. | |
213 | * The complicating detail is that one cannot partially release a reservation. | |
214 | * | |
215 | * The management routines below manage a 'heap' that is composed of several | |
216 | * distinct reservations, laid out in the following order: | |
217 | * 0+ reservations set MEM_COMMIT | |
218 | * 1 reservation starting MEM_COMMIT with an optional MEM_RESERVE tail | |
219 | * | |
220 | * The heap always starts on a reservation and ends at where the MEM_RESERVE | |
221 | * region (if any) begins. | |
222 | */ | |
223 | ||
224 | /* Create a new heap */ | |
225 | static inline void *Windows_mmapAnon (void *base, size_t length) { | |
226 | void *res; | |
227 | ||
228 | /* We prevoiusly used "0" instead of start, which lead to crashes. | |
229 | * After reading win32 documentation, the reason for these crashes | |
230 | * becomes clear: we were using only MEM_COMMIT! If there was memory | |
231 | * decommitted in a previous heap shrink, a new heap might end up | |
232 | * inside the reserved (but uncommitted) memory. When the old heap is | |
233 | * freed, it will kill the new heap as well. This bug will not happen | |
234 | * now because we reserve, then commit. Reserved memory cannot conflict. | |
235 | */ | |
236 | res = VirtualAlloc (base, length, MEM_RESERVE, PAGE_NOACCESS); | |
237 | if (0 == res) | |
238 | return (void*)-1; | |
239 | ||
240 | /* Actually get the memory for use */ | |
241 | if (0 == VirtualAlloc (res, length, MEM_COMMIT, PAGE_READWRITE)) { | |
242 | VirtualFree(res, 0, MEM_RELEASE); | |
243 | return (void*)-1; | |
244 | } | |
245 | ||
246 | return res; | |
247 | } | |
248 | ||
249 | static inline void Windows_release (void *base, size_t length) { | |
250 | MEMORY_BASIC_INFORMATION mi; | |
251 | ||
252 | if (length == 0) return; | |
253 | ||
254 | /* We might not be able to release the first reservation because | |
255 | * it overlaps the base address we wish to keep. The idea is to | |
256 | * decommit the part we don't need, and release all reservations | |
257 | * that may be after this point. | |
258 | */ | |
259 | ||
260 | if (0 == VirtualQuery(base, &mi, sizeof(mi))) | |
261 | die("VirtualQuery failed"); | |
262 | assert (mi.State != MEM_FREE); | |
263 | assert (mi.RegionSize <= length); | |
264 | ||
265 | if (mi.AllocationBase != base) { | |
266 | if (0 == VirtualFree(base, mi.RegionSize, MEM_DECOMMIT)) | |
267 | die("VirtualFree(MEM_DECOMMIT)"); | |
268 | ||
269 | /* Requery: the following region might also be decommit */ | |
270 | VirtualQuery(base, &mi, sizeof(mi)); | |
271 | assert (mi.State == MEM_RESERVE); | |
272 | ||
273 | /* It's possible the consolidated reserved space is larger | |
274 | * than the range we were asked to free. Bail out early. | |
275 | */ | |
276 | if (mi.RegionSize >= length) return; | |
277 | ||
278 | /* Skip decommited region and move to the next reservation */ | |
279 | base = (char*)base + mi.RegionSize; | |
280 | length -= mi.RegionSize; | |
281 | } | |
282 | ||
283 | /* Clean-up the remaining tail. */ | |
284 | while (length > 0) { | |
285 | if (0 == VirtualQuery(base, &mi, sizeof(mi))) | |
286 | die("VirtualQuery"); | |
287 | ||
288 | /* We should never have a completely decommitted alloc */ | |
289 | assert (mi.State == MEM_COMMIT); | |
290 | /* This method is supposed to only do complete releases */ | |
291 | assert (mi.AllocationBase == base); | |
292 | /* The committed region should never exceed the length */ | |
293 | assert (mi.RegionSize <= length); | |
294 | ||
295 | if (0 == VirtualFree(base, 0, MEM_RELEASE)) | |
296 | die("VirtualFree(MEM_RELEASE) failed"); | |
297 | ||
298 | base = (char*)base + mi.RegionSize; | |
299 | length -= mi.RegionSize; | |
300 | } | |
301 | ||
302 | /* The last release also handled the optional MEM_RESERVE region */ | |
303 | } | |
304 | ||
305 | /* Extend an existing heap */ | |
306 | static inline void* Windows_extend (void *base, size_t length) { | |
307 | MEMORY_BASIC_INFORMATION mi; | |
308 | void *end; | |
309 | ||
310 | /* Check the status of memory after the end of the allocation */ | |
311 | VirtualQuery(base, &mi, sizeof(mi)); | |
312 | ||
313 | if (mi.State == MEM_FREE) { | |
314 | /* No tail of reserved memory -> simply try to allocate */ | |
315 | return Windows_mmapAnon(base, length); | |
316 | } else if (mi.State == MEM_RESERVE) { | |
317 | assert (mi.AllocationBase <= base); | |
318 | end = (char*)base + mi.RegionSize; | |
319 | ||
320 | if (mi.RegionSize > length) { /* only commit is needed */ | |
321 | if (0 == VirtualAlloc(base, length, | |
322 | MEM_COMMIT, PAGE_READWRITE)) { | |
323 | return (void*)-1; | |
324 | } else { | |
325 | return base; | |
326 | } | |
327 | } else if (end == Windows_mmapAnon(end, length-mi.RegionSize)) { | |
328 | if (0 == VirtualAlloc(base, mi.RegionSize, | |
329 | MEM_COMMIT, PAGE_READWRITE)) { | |
330 | VirtualFree(end, 0, MEM_RELEASE); | |
331 | return (void*)-1; | |
332 | } else { | |
333 | return base; | |
334 | } | |
335 | } else { | |
336 | /* Failed to allocate tail */ | |
337 | return (void*)-1; | |
338 | } | |
339 | } else { | |
340 | /* The memory is used by another mapping */ | |
341 | return (void*)-1; | |
342 | } | |
343 | } | |
344 | ||
345 | C_Errno_t(C_PId_t) | |
346 | Windows_Process_create (NullString8_t cmds, NullString8_t args, NullString8_t envs, | |
347 | C_Fd_t in, C_Fd_t out, C_Fd_t err) { | |
348 | char *cmd; | |
349 | char *arg; | |
350 | char *env; | |
351 | STARTUPINFO si; | |
352 | PROCESS_INFORMATION pi; | |
353 | ||
354 | cmd = (char*)cmds; | |
355 | arg = (char*)args; | |
356 | env = (char*)envs; | |
357 | ||
358 | ZeroMemory (&si, sizeof(STARTUPINFO)); | |
359 | si.cb = sizeof(STARTUPINFO); | |
360 | si.hStdInput = dupHandle (in); | |
361 | si.hStdOutput = dupHandle (out); | |
362 | si.hStdError = dupHandle (err); | |
363 | si.dwFlags = STARTF_USESTDHANDLES; /* use the above */ | |
364 | if (!si.hStdInput or !si.hStdOutput or !si.hStdError) { | |
365 | if (si.hStdInput) CloseHandle (si.hStdInput); | |
366 | if (si.hStdOutput) CloseHandle (si.hStdOutput); | |
367 | if (si.hStdError) CloseHandle (si.hStdError); | |
368 | /* errno already faked by dupHandle */ | |
369 | return -1; | |
370 | } | |
371 | ZeroMemory (&pi, sizeof(PROCESS_INFORMATION)); | |
372 | unless (CreateProcess ( | |
373 | cmd, /* Module name */ | |
374 | arg, /* Command line */ | |
375 | NULL, /* Process handle not inheritable */ | |
376 | NULL, /* Thread handle not inheritable */ | |
377 | TRUE, /* Set handle inheritance to TRUE */ | |
378 | 0, /* No creation flags */ | |
379 | env, /* Environment */ | |
380 | NULL, /* Use parent's starting directory */ | |
381 | &si, /* Pointer to STARTUPINFO structure */ | |
382 | &pi /* Pointer to PROCESS_INFORMATION structure */ | |
383 | )) { | |
384 | errno = ENOENT; /* probably does not exist (aka ENOFILE)*/ | |
385 | return -1; | |
386 | } | |
387 | /* Process created successfully */ | |
388 | /* We will return the process handle for the 'pid'. | |
389 | * This way we can TerminateProcess (kill) it and | |
390 | * WaitForSingleObject/GetExitCodeProcess (reap) it. | |
391 | * The thread handle is not needed, so clean it. | |
392 | */ | |
393 | CloseHandle (pi.hThread); | |
394 | CloseHandle (si.hStdInput); | |
395 | CloseHandle (si.hStdOutput); | |
396 | CloseHandle (si.hStdError); | |
397 | ||
398 | return (C_PId_t)pi.hProcess; | |
399 | } | |
400 | ||
401 | C_Errno_t(C_PId_t) | |
402 | Windows_Process_createNull (NullString8_t cmds, NullString8_t args, | |
403 | C_Fd_t in, C_Fd_t out, C_Fd_t err) { | |
404 | return Windows_Process_create (cmds, args, NULL, in, out, err); | |
405 | } | |
406 | ||
407 | C_Errno_t(C_Int_t) Windows_Process_getexitcode (C_PId_t pid, Ref(C_Status_t) status) { | |
408 | HANDLE h; | |
409 | ||
410 | h = (HANDLE)pid; | |
411 | unless (WaitForSingleObject (h, INFINITE) == WAIT_OBJECT_0) { | |
412 | errno = ECHILD; | |
413 | return -1; | |
414 | } | |
415 | unless (GetExitCodeProcess (h, (DWORD*)status)) { | |
416 | errno = ECHILD; | |
417 | return -1; | |
418 | } | |
419 | return 0; | |
420 | } | |
421 | ||
422 | C_Errno_t(C_Int_t) Windows_Process_terminate (C_PId_t pid, C_Signal_t sig) { | |
423 | HANDLE h; | |
424 | ||
425 | h = (HANDLE)pid; | |
426 | unless (TerminateProcess (h, 0x80000000UL | sig)) { | |
427 | errno = ECHILD; | |
428 | return -1; | |
429 | } | |
430 | return 0; | |
431 | } |