0.9.18.6: Win32 get-internal-real-time improved
[sbcl.git] / src / runtime / win32-os.c
1 /*
2  * the Win32 incarnation of OS-dependent routines.  See also
3  * $(sbcl_arch)-win32-os.c
4  *
5  * This file (along with os.h) exports an OS-independent interface to
6  * the operating system VM facilities. Surprise surprise, this
7  * interface looks a lot like the Mach interface (but simpler in some
8  * places). For some operating systems, a subset of these functions
9  * will have to be emulated.
10  */
11
12 /*
13  * This software is part of the SBCL system. See the README file for
14  * more information.
15  *
16  * This software is derived from the CMU CL system, which was
17  * written at Carnegie Mellon University and released into the
18  * public domain. The software is in the public domain and is
19  * provided with absolutely no warranty. See the COPYING and CREDITS
20  * files for more information.
21  */
22
23 /*
24  * This file was copied from the Linux version of the same, and
25  * likely still has some linuxisms in it have haven't been elimiated
26  * yet.
27  */
28
29 #include <malloc.h>
30 #include <stdio.h>
31 #include <sys/param.h>
32 #include <sys/file.h>
33 #include <io.h>
34 #include "sbcl.h"
35 #include "./signal.h"
36 #include "os.h"
37 #include "arch.h"
38 #include "globals.h"
39 #include "sbcl.h"
40 #include "interrupt.h"
41 #include "interr.h"
42 #include "lispregs.h"
43 #include "runtime.h"
44 #include "alloc.h"
45 #include "genesis/primitive-objects.h"
46
47 #include <sys/types.h>
48 #include <signal.h>
49 #include <sys/time.h>
50 #include <sys/stat.h>
51 #include <unistd.h>
52 #include <shlobj.h>
53
54 #include <excpt.h>
55
56 #include "validate.h"
57 #include "thread.h"
58 size_t os_vm_page_size;
59
60
61 #include "gc.h"
62 #include "gencgc-internal.h"
63
64 #if 0
65 int linux_sparc_siginfo_bug = 0;
66 int linux_supports_futex=0;
67 #endif
68
69 /* The exception handling function looks like this: */
70 EXCEPTION_DISPOSITION handle_exception(EXCEPTION_RECORD *,
71                                        struct lisp_exception_frame *,
72                                        CONTEXT *,
73                                        void *);
74
75 void *base_seh_frame;
76
77 static void *get_seh_frame(void)
78 {
79     void* retval;
80     asm volatile ("movl %%fs:0,%0": "=r" (retval));
81     return retval;
82 }
83
84 static void set_seh_frame(void *frame)
85 {
86     asm volatile ("movl %0,%%fs:0": : "r" (frame));
87 }
88
89 static struct lisp_exception_frame *find_our_seh_frame(void)
90 {
91     struct lisp_exception_frame *frame = get_seh_frame();
92
93     while (frame->handler != handle_exception)
94         frame = frame->next_frame;
95
96     return frame;
97 }
98
99 #if 0
100 inline static void *get_stack_frame(void)
101 {
102     void* retval;
103     asm volatile ("movl %%ebp,%0": "=r" (retval));
104     return retval;
105 }
106 #endif
107
108 void os_init(char *argv[], char *envp[])
109 {
110     SYSTEM_INFO system_info;
111
112     GetSystemInfo(&system_info);
113     os_vm_page_size = system_info.dwPageSize;
114
115     base_seh_frame = get_seh_frame();
116 }
117
118
119 /*
120  * So we have three fun scenarios here.
121  *
122  * First, we could be being called to reserve the memory areas
123  * during initialization (prior to loading the core file).
124  *
125  * Second, we could be being called by the GC to commit a page
126  * that has just been decommitted (for easy zero-fill).
127  *
128  * Third, we could be being called by create_thread_struct()
129  * in order to create the sundry and various stacks.
130  *
131  * The third case is easy to pick out because it passes an
132  * addr of 0.
133  *
134  * The second case is easy to pick out because it will be for
135  * a range of memory that is MEM_RESERVE rather than MEM_FREE.
136  *
137  * The second case is also an easy implement, because we leave
138  * the memory as reserved (since we do lazy commits).
139  */
140
141 os_vm_address_t
142 os_validate(os_vm_address_t addr, os_vm_size_t len)
143 {
144     MEMORY_BASIC_INFORMATION mem_info;
145
146     if (!addr) {
147         /* the simple case first */
148         os_vm_address_t real_addr;
149         if (!(real_addr = VirtualAlloc(addr, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE))) {
150             fprintf(stderr, "VirtualAlloc: 0x%lx.\n", GetLastError());
151             return 0;
152         }
153
154         return real_addr;
155     }
156
157     if (!VirtualQuery(addr, &mem_info, sizeof mem_info)) {
158         fprintf(stderr, "VirtualQuery: 0x%lx.\n", GetLastError());
159         return 0;
160     }
161
162     if ((mem_info.State == MEM_RESERVE) && (mem_info.RegionSize >=len)) return addr;
163
164     if (mem_info.State == MEM_RESERVE) {
165         fprintf(stderr, "validation of reserved space too short.\n");
166         fflush(stderr);
167     }
168
169     if (!VirtualAlloc(addr, len, (mem_info.State == MEM_RESERVE)? MEM_COMMIT: MEM_RESERVE, PAGE_EXECUTE_READWRITE)) {
170         fprintf(stderr, "VirtualAlloc: 0x%lx.\n", GetLastError());
171         return 0;
172     }
173
174     return addr;
175 }
176
177 /*
178  * For os_invalidate(), we merely decommit the memory rather than
179  * freeing the address space. This loses when freeing per-thread
180  * data and related memory since it leaks address space. It's not
181  * too lossy, however, since the two scenarios I'm aware of are
182  * fd-stream buffers, which are pooled rather than torched, and
183  * thread information, which I hope to pool (since windows creates
184  * threads at its own whim, and we probably want to be able to
185  * have them callback without funky magic on the part of the user,
186  * and full-on thread allocation is fairly heavyweight). Someone
187  * will probably shoot me down on this with some pithy comment on
188  * the use of (setf symbol-value) on a special variable. I'm happy
189  * for them.
190  */
191
192 void
193 os_invalidate(os_vm_address_t addr, os_vm_size_t len)
194 {
195     if (!VirtualFree(addr, len, MEM_DECOMMIT)) {
196         fprintf(stderr, "VirtualFree: 0x%lx.\n", GetLastError());
197     }
198 }
199
200 /*
201  * os_map() is called to map a chunk of the core file into memory.
202  *
203  * Unfortunately, Windows semantics completely screws this up, so
204  * we just add backing store from the swapfile to where the chunk
205  * goes and read it up like a normal file. We could consider using
206  * a lazy read (demand page) setup, but that would mean keeping an
207  * open file pointer for the core indefinately (and be one more
208  * thing to maintain).
209  */
210
211 os_vm_address_t
212 os_map(int fd, int offset, os_vm_address_t addr, os_vm_size_t len)
213 {
214     os_vm_size_t count;
215
216 #if 0
217     fprintf(stderr, "os_map: %d, 0x%x, %p, 0x%x.\n", fd, offset, addr, len);
218     fflush(stderr);
219 #endif
220
221     if (!VirtualAlloc(addr, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) {
222         fprintf(stderr, "VirtualAlloc: 0x%lx.\n", GetLastError());
223         lose("os_map: VirtualAlloc failure");
224     }
225
226     if (lseek(fd, offset, SEEK_SET) == -1) {
227         lose("os_map: Seek failure.");
228     }
229
230     count = read(fd, addr, len);
231     if (count != len) {
232         fprintf(stderr, "expected 0x%x, read 0x%x.\n", len, count);
233         lose("os_map: Failed to read enough bytes.");
234     }
235
236     return addr;
237 }
238
239 static DWORD os_protect_modes[8] = {
240     PAGE_NOACCESS,
241     PAGE_READONLY,
242     PAGE_READWRITE,
243     PAGE_READWRITE,
244     PAGE_EXECUTE,
245     PAGE_EXECUTE_READ,
246     PAGE_EXECUTE_READWRITE,
247     PAGE_EXECUTE_READWRITE,
248 };
249
250 void
251 os_protect(os_vm_address_t address, os_vm_size_t length, os_vm_prot_t prot)
252 {
253     DWORD old_prot;
254
255     if (!VirtualProtect(address, length, os_protect_modes[prot], &old_prot)) {
256         fprintf(stderr, "VirtualProtect failed, code 0x%lx.\n", GetLastError());
257         fflush(stderr);
258     }
259 }
260
261 /* FIXME: Now that FOO_END, rather than FOO_SIZE, is the fundamental
262  * description of a space, we could probably punt this and just do
263  * (FOO_START <= x && x < FOO_END) everywhere it's called. */
264 static boolean
265 in_range_p(os_vm_address_t a, lispobj sbeg, size_t slen)
266 {
267     char* beg = (char*)((long)sbeg);
268     char* end = (char*)((long)sbeg) + slen;
269     char* adr = (char*)a;
270     return (adr >= beg && adr < end);
271 }
272
273 boolean
274 is_linkage_table_addr(os_vm_address_t addr)
275 {
276     return in_range_p(addr, LINKAGE_TABLE_SPACE_START, LINKAGE_TABLE_SPACE_END);
277 }
278
279 boolean
280 is_valid_lisp_addr(os_vm_address_t addr)
281 {
282     struct thread *th;
283     if(in_range_p(addr, READ_ONLY_SPACE_START, READ_ONLY_SPACE_SIZE) ||
284        in_range_p(addr, STATIC_SPACE_START   , STATIC_SPACE_SIZE) ||
285        in_range_p(addr, DYNAMIC_SPACE_START  , DYNAMIC_SPACE_SIZE))
286         return 1;
287     for_each_thread(th) {
288         if(((os_vm_address_t)th->control_stack_start <= addr) && (addr < (os_vm_address_t)th->control_stack_end))
289             return 1;
290         if(in_range_p(addr, (unsigned long)th->binding_stack_start, BINDING_STACK_SIZE))
291             return 1;
292     }
293     return 0;
294 }
295
296 /*
297  * any OS-dependent special low-level handling for signals
298  */
299
300 /* A tiny bit of interrupt.c state we want our paws on. */
301 extern boolean internal_errors_enabled;
302
303 /*
304  * FIXME: There is a potential problem with foreign code here.
305  * If we are running foreign code instead of lisp code and an
306  * exception occurs we arrange a call into Lisp. If the
307  * foreign code has installed an exception handler, we run the
308  * very great risk of throwing through their exception handler
309  * without asking it to unwind. This is more a problem with
310  * non-sigtrap (EXCEPTION_BREAKPOINT) exceptions, as they could
311  * reasonably be expected to happen in foreign code. We need to
312  * figure out the exception handler unwind semantics and adhere
313  * to them (probably by abusing the Lisp unwind-protect system)
314  * if we are going to handle this scenario correctly.
315  *
316  * A good explanation of the exception handling semantics is
317  * http://win32assembly.online.fr/Exceptionhandling.html .
318  * We will also need to handle this ourselves when foreign
319  * code tries to unwind -us-.
320  *
321  * When unwinding through foreign code we should unwind the
322  * Lisp stack to the entry from foreign code, then unwind the
323  * foreign code stack to the entry from Lisp, then resume
324  * unwinding in Lisp.
325  */
326
327 EXCEPTION_DISPOSITION sigtrap_emulator(CONTEXT *context,
328                                        struct lisp_exception_frame *exception_frame)
329 {
330     if (*((char *)context->Eip + 1) == trap_ContextRestore) {
331         /* This is the cleanup for what is immediately below, and
332          * for the generic exception handling further below. We
333          * have to memcpy() the original context (emulated sigtrap
334          * or normal exception) over our context and resume it. */
335         memcpy(context, &exception_frame->context, sizeof(CONTEXT));
336         return ExceptionContinueExecution;
337
338     } else {
339         /* Not a trap_ContextRestore, must be a sigtrap.
340          * sigtrap_trampoline is defined in x86-assem.S. */
341         extern void sigtrap_trampoline;
342
343         /*
344          * Unlike some other operating systems, Win32 leaves EIP
345          * pointing to the breakpoint instruction.
346          */
347         context->Eip++;
348
349         /* We're not on an alternate stack like we would be in some
350          * other operating systems, and we don't want to risk leaking
351          * any important resources if we throw out of the sigtrap
352          * handler, so we need to copy off our context to a "safe"
353          * place and then monkey with the return EIP to point to a
354          * trampoline which calls another function which copies the
355          * context out to a really-safe place and then calls the real
356          * sigtrap handler. When the real sigtrap handler returns, the
357          * trampoline then contains another breakpoint with a code of
358          * trap_ContextRestore (see above). Essentially the same
359          * mechanism is used by the generic exception path. There is
360          * a small window of opportunity between us copying the
361          * context to the "safe" place and the sigtrap wrapper copying
362          * it to the really-safe place (allocated in its stack frame)
363          * during which the context can be smashed. The only scenario
364          * I can come up with for this, however, involves a stack
365          * overflow occuring at just the wrong time (which makes one
366          * wonder how stack overflow exceptions even happen, given
367          * that we don't switch stacks for exception processing...) */
368         memcpy(&exception_frame->context, context, sizeof(CONTEXT));
369
370         /* FIXME: Why do we save the old EIP in EAX? The sigtrap_trampoline
371          * pushes it into stack, but the sigtrap_wrapper where the trampoline
372          * goes ignores it, and after the wrapper we hit the trap_ContextRestore,
373          * which nukes the whole context with the original one?
374          *
375          * Am I misreading this, or is the EAX here and in the
376          * trampoline superfluous? --NS 20061024 */
377         context->Eax = context->Eip;
378         context->Eip = (unsigned long)&sigtrap_trampoline;
379
380         /* and return */
381         return ExceptionContinueExecution;
382     }
383 }
384
385 void sigtrap_wrapper(void)
386 {
387     /*
388      * This is the wrapper around the sigtrap handler called from
389      * the trampoline returned to from the function above.
390      *
391      * There actually is a point to some of the commented-out code
392      * in this function, although it really belongs to the callback
393      * wrappers. Once it is installed there, it can probably be
394      * removed from here.
395      */
396     extern void sigtrap_handler(int signal, siginfo_t *info, void *context);
397
398 /*     volatile struct { */
399 /*      void *handler[2]; */
400     CONTEXT context;
401 /*     } handler; */
402
403     struct lisp_exception_frame *frame = find_our_seh_frame();
404
405 /*     wos_install_interrupt_handlers(handler); */
406 /*     handler.handler[0] = get_seh_frame(); */
407 /*     handler.handler[1] = &handle_exception; */
408 /*     set_seh_frame(&handler); */
409
410     memcpy(&context, &frame->context, sizeof(CONTEXT));
411     sigtrap_handler(0, NULL, &context);
412     memcpy(&frame->context, &context, sizeof(CONTEXT));
413
414 /*     set_seh_frame(handler.handler[0]); */
415 }
416
417 EXCEPTION_DISPOSITION handle_exception(EXCEPTION_RECORD *exception_record,
418                                        struct lisp_exception_frame *exception_frame,
419                                        CONTEXT *context,
420                                        void *dc) /* FIXME: What's dc again? */
421 {
422
423     /* For EXCEPTION_ACCESS_VIOLATION only. */
424     void *fault_address = (void *)exception_record->ExceptionInformation[1];
425
426     if (exception_record->ExceptionCode == EXCEPTION_BREAKPOINT) {
427         /* Pick off sigtrap case first. */
428         return sigtrap_emulator(context, exception_frame);
429
430     }
431     else if (exception_record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
432              (is_valid_lisp_addr(fault_address) ||
433               is_linkage_table_addr(fault_address))) {
434         /* Pick off GC-related memory fault next. */
435         MEMORY_BASIC_INFORMATION mem_info;
436
437         if (!VirtualQuery(fault_address, &mem_info, sizeof mem_info)) {
438             fprintf(stderr, "VirtualQuery: 0x%lx.\n", GetLastError());
439             lose("handle_exception: VirtualQuery failure");
440         }
441
442         if (mem_info.State == MEM_RESERVE) {
443             /* First use new page, lets get some memory for it. */
444             if (!VirtualAlloc(mem_info.BaseAddress, os_vm_page_size,
445                               MEM_COMMIT, PAGE_EXECUTE_READWRITE)) {
446                 fprintf(stderr, "VirtualAlloc: 0x%lx.\n", GetLastError());
447                 lose("handle_exception: VirtualAlloc failure");
448
449             } else {
450                 /*
451                  * Now, if the page is supposedly write-protected and this
452                  * is a write, tell the gc that it's been hit.
453                  *
454                  * FIXME: Are we supposed to fall-through to the Lisp
455                  * exception handler if the gc doesn't take the wp violation?
456                  */
457                 if (exception_record->ExceptionInformation[0]) {
458                     int index = find_page_index(fault_address);
459                     if ((index != -1) && (page_table[index].write_protected)) {
460                         gencgc_handle_wp_violation(fault_address);
461                     }
462                 }
463                 return ExceptionContinueExecution;
464             }
465
466         } else if (gencgc_handle_wp_violation(fault_address)) {
467             /* gc accepts the wp violation, so resume where we left off. */
468             return ExceptionContinueExecution;
469         }
470
471         /* All else failed, drop through to the lisp-side exception handler. */
472     }
473
474     /*
475      * If we fall through to here then we need to either forward
476      * the exception to the lisp-side exception handler if it's
477      * set up, or drop to LDB.
478      */
479
480     if (internal_errors_enabled) {
481         /* exception_trampoline is defined in x86-assem.S. */
482         extern void exception_trampoline;
483
484         /* We're making the somewhat arbitrary decision that having
485          * internal errors enabled means that lisp has sufficient
486          * marbles to be able to handle exceptions, but xceptions
487          * aren't supposed to happen during cold init or reinit
488          * anyway.
489          *
490          * We use the same mechanism as the sigtrap emulator above
491          * with just a couple changes. We obviously use a different
492          * trampoline and wrapper function, we kill out any live
493          * floating point exceptions, and we save off the exception
494          * record as well as the context. */
495
496         /* Save off context and exception information */
497         memcpy(&exception_frame->context, context, sizeof(CONTEXT));
498         memcpy(&exception_frame->exception, exception_record, sizeof(EXCEPTION_RECORD));
499
500         /* Set up to activate trampoline when we return
501          *
502          * FIXME: Why do we save the old EIP in EAX? The
503          * exception_trampoline pushes it into stack, but the wrapper
504          * where the trampoline goes ignores it, and then the wrapper
505          * unwinds from Lisp... WTF?
506          *
507          * Am I misreading this, or is the EAX here and in the
508          * trampoline superfluous? --NS 20061024 */
509         context->Eax = context->Eip;
510         context->Eip = (unsigned long)&exception_trampoline;
511
512         /* Make sure a floating-point trap doesn't kill us */
513         context->FloatSave.StatusWord &= ~0x3f;
514
515         /* And return. */
516         return ExceptionContinueExecution;
517     }
518
519     fprintf(stderr, "Exception Code: 0x%lx.\n", exception_record->ExceptionCode);
520     fprintf(stderr, "Faulting IP: 0x%lx.\n", (DWORD)exception_record->ExceptionAddress);
521     if (exception_record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
522         MEMORY_BASIC_INFORMATION mem_info;
523
524         if (VirtualQuery(fault_address, &mem_info, sizeof mem_info)) {
525             fprintf(stderr, "page status: 0x%lx.\n", mem_info.State);
526         }
527
528         fprintf(stderr, "Was writing: %ld, where: 0x%lx.\n",
529                 exception_record->ExceptionInformation[0],
530                 (DWORD)fault_address);
531     }
532
533     fflush(stderr);
534
535     fake_foreign_function_call(context);
536     lose("fake_foreign_function_call fell through");
537
538     /* FIXME: WTF? How are we supposed to end up here? */
539     return ExceptionContinueSearch;
540 }
541
542 void handle_win32_exception_wrapper(void)
543 {
544     struct lisp_exception_frame *frame = find_our_seh_frame();
545     CONTEXT context;
546     EXCEPTION_RECORD exception_record;
547     lispobj context_sap;
548     lispobj exception_record_sap;
549
550     memcpy(&context, &frame->context, sizeof(CONTEXT));
551     memcpy(&exception_record, &frame->exception, sizeof(EXCEPTION_RECORD));
552
553     fake_foreign_function_call(&context);
554
555     /* Allocate the SAP objects while the "interrupts" are still
556      * disabled. */
557     context_sap = alloc_sap(&context);
558     exception_record_sap = alloc_sap(&exception_record);
559
560     funcall2(SymbolFunction(HANDLE_WIN32_EXCEPTION), context_sap,
561              exception_record_sap);
562
563     /* FIXME: These never happen, as the Lisp-side call is
564      * to an ERROR, which means we must do a non-local exit
565      */
566     undo_fake_foreign_function_call(&context);
567     memcpy(&frame->context, &context, sizeof(CONTEXT));
568 }
569
570 void
571 wos_install_interrupt_handlers(struct lisp_exception_frame *handler)
572 {
573     handler->next_frame = get_seh_frame();
574     handler->handler = &handle_exception;
575     set_seh_frame(handler);
576 }
577
578 void bcopy(const void *src, void *dest, size_t n)
579 {
580     MoveMemory(dest, src, n);
581 }
582
583 /*
584  * The stubs below are replacements for the windows versions,
585  * which can -fail- when used in our memory spaces because they
586  * validate the memory spaces they are passed in a way that
587  * denies our exception handler a chance to run.
588  */
589
590 void *memmove(void *dest, const void *src, size_t n)
591 {
592     if (dest < src) {
593         int i;
594         for (i = 0; i < n; i++) *(((char *)dest)+i) = *(((char *)src)+i);
595     } else {
596         while (n--) *(((char *)dest)+n) = *(((char *)src)+n);
597     }
598     return dest;
599 }
600
601 void *memcpy(void *dest, const void *src, size_t n)
602 {
603     while (n--) *(((char *)dest)+n) = *(((char *)src)+n);
604     return dest;
605 }
606
607 char *dirname(char *path)
608 {
609     static char buf[PATH_MAX + 1];
610     size_t pathlen = strlen(path);
611     int i;
612
613     if (pathlen >= sizeof(buf)) {
614         lose("Pathname too long in dirname.\n");
615         return NULL;
616     }
617
618     strcpy(buf, path);
619     for (i = pathlen; i >= 0; --i) {
620         if (buf[i] == '/' || buf[i] == '\\') {
621             buf[i] = '\0';
622             break;
623         }
624     }
625
626     return buf;
627 }
628
629 /* This is a manually-maintained version of ldso_stubs.S. */
630
631 void scratch(void)
632 {
633     CloseHandle(0);
634     FlushConsoleInputBuffer(0);
635     FormatMessageA(0, 0, 0, 0, 0, 0, 0);
636     FreeLibrary(0);
637     GetACP();
638     GetConsoleCP();
639     GetConsoleOutputCP();
640     GetCurrentProcess();
641     GetExitCodeProcess(0, 0);
642     GetLastError();
643     GetOEMCP();
644     GetProcAddress(0, 0);
645     GetProcessTimes(0, 0, 0, 0, 0);
646     GetSystemTimeAsFileTime(0);
647     LoadLibrary(0);
648     LocalFree(0);
649     PeekConsoleInput(0, 0, 0, 0);
650     PeekNamedPipe(0, 0, 0, 0, 0, 0);
651     ReadFile(0, 0, 0, 0, 0);
652     Sleep(0);
653     WriteFile(0, 0, 0, 0, 0);
654     _get_osfhandle(0);
655     _pipe(0,0,0);
656     access(0,0);
657     acos(0);
658     asin(0);
659     close(0);
660     cosh(0);
661     dup(0);
662     hypot(0, 0);
663     isatty(0);
664     sinh(0);
665     strerror(42);
666     write(0, 0, 0);
667     #ifndef LISP_FEATURE_SB_UNICODE
668       CreateDirectoryA(0,0);
669       GetComputerNameA(0, 0);
670       GetCurrentDirectoryA(0,0);
671       GetEnvironmentVariableA(0, 0, 0);
672       GetVersionExA(0);
673       MoveFileA(0,0);
674       SHGetFolderPathA(0, 0, 0, 0, 0);
675       SetCurrentDirectoryA(0);
676       SetEnvironmentVariableA(0, 0);
677     #else
678       CreateDirectoryW(0,0);
679       FormatMessageW(0, 0, 0, 0, 0, 0, 0);
680       GetComputerNameW(0, 0);
681       GetCurrentDirectoryW(0,0);
682       GetEnvironmentVariableW(0, 0, 0);
683       GetVersionExW(0);
684       MoveFileW(0,0);
685       SHGetFolderPathW(0, 0, 0, 0, 0);
686       SetCurrentDirectoryW(0);
687       SetEnvironmentVariableW(0, 0);
688     #endif
689 }
690
691 char *
692 os_get_runtime_executable_path()
693 {
694     char path[MAX_PATH + 1];
695     DWORD bufsize = sizeof(path);
696     DWORD size;
697
698     if ((size = GetModuleFileNameA(NULL, path, bufsize)) == 0)
699         return NULL;
700     else if (size == bufsize && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
701         return NULL;
702
703     return copied_string(path);
704 }
705
706 /* EOF */