/* fork.cc: fork for WIN32. Copyright 1996, 1997 Cygnus Solutions This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "winsup.h" extern "C" struct _reent reent_data; /* Timeout to wait for child to start, parent to init child, etc. */ /* FIXME: Once things stabilize, bump up to a few minutes. */ #define FORK_WAIT_TIMEOUT (120 * 1000 /* 120 seconds */) /* Error return code from WaitForSingleObject. */ #define WAIT_ERROR_RC 0xffffffff /* Mutexes and events necessary for child startup. */ static HANDLE forkee_stopped, forker_stopped; /* Initialize the fork mechanism. */ void fork_init () { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof (sa); sa.lpSecurityDescriptor = get_null_sd(); sa.bInheritHandle = 0; forker_stopped = CreateEventA (&sa, TRUE, TRUE, "cygwin.forker-stopped"); forkee_stopped = CreateEventA (&sa, TRUE, TRUE, "cygwin.forkee-stopped"); /* If we didn't obtain all the resources we need to fork, allow the program to continue, but record the fact that fork won't work. */ if (forker_stopped == NULL || forkee_stopped == NULL) { system_printf ("fork_init: unable to allocate fork() resources.\n"); system_printf ("fork_init: fork() disabled.\n"); } } /* Undo what fork_init does. If a task is shutting down, this isn't really necessary, but maybe one day we'll want to allow cygwin.dll to be attached to and detached from, so we should provide a clean interface to release all resources we obtain. */ void fork_terminate () { CloseHandle (forker_stopped); CloseHandle (forkee_stopped); } #if 0 void print_checksum (int idx, register void *low, register void *high) { int pi; register int sum = 0; small_printf ("CK %d %x %x ", idx, low, high); for (int *pi = (int *)low; pi < (int *)high; pi++) { sum += *pi; } small_printf ("%x\n", sum); } #endif /* Copy memory from parent to child. The result is a boolean indicating success. */ int copy_memory_to_forkee (HANDLE child, void *low, void *high, int) { DWORD done; int res; debug_printf ("fork copy: child handle %d, low %p, high %p\n", child, low, high); int lump = 1024*64; for (char *p = (char *) low; p < (char *) high; p += lump) { int todo = MIN ((char *)high - p, lump); res = WriteProcessMemory (child, p, p, todo, &done); if (!res || todo != done) { if (!res) __seterrno (); /* This happens when it shouldn't so there's a bug in our fork implementation somewhere. Print a message on the console to call people's attention to the failure until we get it straightened out. */ small_printf ("fork copy: failed, %p..%p, res is %d, done is %d\n", low, high, res, done); /* Call debug_printf as well to make sure message gets in log file if there is one. */ debug_printf ("fork copy: failed\n"); return 0; } } #if 0 print_checksum (idx, low, high); #endif debug_printf ("fork copy: done\n"); return 1; } int copy_mmap_records_to_forkee(pinfo *child); int recreate_mmaps_after_fork(void *); int copy_copyonwrite_mmaps_to_forkee(HANDLE hChild); /* Main guts of fork implementation. The result is the standard result of fork. */ static int cygwin_fork_helper1 (void *proc_data_start, void *proc_data_end, void *proc_bss_start, void *proc_bss_end) { int res; int x, rc; hinfo *child_hinfo = 0; if (u->self->split_heap_p) { small_printf ("The heap has been split, CYGWIN can't fork this process.\n"); small_printf ("Increase the heap_chunk_size in the registry and try again.\n"); set_errno (ENOMEM); syscall_printf ("-1 = fork (), split heap\n"); return -1; } /* Don't start the fork until we have the lock. */ rc = lock_pinfo_for_update(FORK_WAIT_TIMEOUT); switch (rc) { case WAIT_ERROR_RC: small_printf ("fork parent: WaitForSingleObject (mutex) failed, win32 error %d\n", GetLastError ()); set_errno (EAGAIN); syscall_printf ("-1 = fork (), wait failed\n"); return -1; case WAIT_TIMEOUT: small_printf ("fork parent: WaitForSingleObject (mutex) timed out\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork (), wait timed out\n"); return -1; default: debug_printf ("fork_helper: %d = WaitForSingleObject (...)\n", rc); break; } pinfo *child = s->p.allocate_pid (); if (!child) { set_errno (EAGAIN); syscall_printf ("-1 = fork (), process table full\n"); unlock_pinfo(); return -1; } /* * We need to save this allocated pointer as the child * will be changing it, plus we need to delete [] it later. */ child_hinfo = child->hmap.vec; /* This will help some of the confusion. */ fflush (stdout); debug_printf ("fork_helper: parent pid is %d, child pid is %d\n", u->self->get_pid (), child->get_pid ()); /* Initialize things that are done later in dll_crt0_1 that aren't done for the forkee. */ child->reent_save = reent_data; child->progname = u->self->progname; /* Copy all the handles we need in the child. */ u->self->hmap.dup_for_fork (&child->hmap); PROCESS_INFORMATION pi = {0}; STARTUPINFO si = {0}; si.cb = sizeof (STARTUPINFO); int c_flags = GetPriorityClass (GetCurrentProcess ()) | CREATE_SUSPENDED; syscall_printf ("CreateProcessA (%s, %s,0,0,1,%x, 0,0,%p,%p)\n", u->self->progname, u->self->progname, c_flags, &si, &pi); rc = CreateProcessA (u->self->progname, /* image to run */ u->self->progname, /* what we send in arg0 */ 0, /* process security attrs */ 0, /* thread security attrs */ TRUE, /* inherit handles from parent */ c_flags, 0, /* use parent's environment */ 0, /* use current drive/directory */ &si, &pi); if (!rc) { __seterrno (); syscall_printf ("-1 = fork(), CreateProcessA failed\n"); child->inuse_p = PID_NOT_IN_USE; delete [] child_hinfo; unlock_pinfo(); return -1; } ResetEvent (forker_stopped); ResetEvent (forkee_stopped); debug_printf ("fork_helper: about to call setjmp\n"); x = setjmp (u->self->restore); debug_printf ("fork_helper: setjmp returned %d\n", x); #if 0 if (0) { int k; unsigned char *p = (unsigned char *)(u->restore[32/4]); for (k = 0; k < 11; k++) small_printf ("set reg %d %x\n", k *4 , u->restore[k]); for (k = 0; k < 11; k++) small_printf ("[%02x]", p[k]); small_printf ("the res was %d\n", x); } #endif if (x == 0) { /* Parent. */ dump_jmp_buf (u->self->restore); #if 0 /* for debugging */ s->base[0][0] = proc_data_start; s->base[0][1] = proc_data_end; s->base[1][0] = proc_bss_start; s->base[1][1] = proc_bss_end; s->base[2][0] = u->base; s->base[2][1] = u->ptr; s->base[3][0] = &x; s->base[3][1] = u->initial_sp; #endif /* Tell the child it's being forked and its pid. Remember, *u gets copied to the child's address space. */ u->forkee = child->get_pid (); /* Fill in fields in the child's process table entry. */ child->ppid = u->self->get_pid (); child->hThread = pi.hThread; child->hProcess = pi.hProcess; child->dwProcessId = pi.dwProcessId; child->uid = u->self->uid; child->gid = u->self->gid; child->sigs = u->self->sigs; child->sig_mask = u->self->sig_mask; if (copy_mmap_records_to_forkee(child)) { small_printf ("fork_helper: copy of mmap_records failed\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), copy of mmap_records failed\n"); TerminateProcess (child->hProcess, 1); u->forkee = 0; goto cleanup; } /* Initialize the child's .data and .bss. */ rc = copy_memory_to_forkee (pi.hProcess, (char *)proc_data_start, (char *)proc_data_end, 0); if (rc) rc = copy_memory_to_forkee (pi.hProcess, (char *)proc_bss_start, (char *)proc_bss_end, 1); if (! rc) { small_printf ("fork_helper: copy of data/bss failed\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), data/bss copy failed\n"); TerminateProcess (child->hProcess, 1); u->forkee = 0; goto cleanup; } u->forkee = 0; /* Need to record that task was started by cygwin32 task. This info will then used by the exit() handling code to know whether to reset inuse_p. Normally inuse_p isn't reset until wait() is called but if the task wasn't started by a cygwin32 task, wait() will never be called and the process table will fill up. We remove this flag if the parent exits. */ child->inuse_p |= PID_TO_BE_WAITED_FOR; /* FIXME: Exit handling code also needs to record that the task ended so the ps command will know about zombies. */ /* Start thread, and wait for it to initialize itself. */ rc = ResumeThread (child->hThread); if (rc != 1) { /* Can't resume the thread. Not sure why this would happen unless there's a bug in the system. Things seem to be working OK now though, so flag this with EAGAIN, but print a message on the console. */ small_printf ("fork_helper: ResumeThread failed, rc = %d\n", rc); set_errno (EAGAIN); syscall_printf ("-1 = fork(), ResumeThread failed\n"); TerminateProcess (child->hProcess, 1); goto cleanup; } debug_printf ("fork_helper: child started\n"); /* We don't want to wait forever here. If there's a problem somewhere it'll hang the entire system (since all forks are mutex'd). If we time out, set errno = EAGAIN and hope the app tries again. */ /* We also add child->hProcess to the wait. If the child fails to initialize (eg. because of a missing dll). Then this handle will become signalled. This stops a *looong* timeout wait. */ HANDLE wait_array[2]; wait_array[0] = child->hProcess; wait_array[1] = forkee_stopped; rc = WaitForMultipleObjects (2, wait_array, FALSE, FORK_WAIT_TIMEOUT); if (rc == WAIT_ERROR_RC || rc == WAIT_TIMEOUT) { if (rc == WAIT_ERROR_RC) small_printf ("fork_helper: WaitForMultipleObjects failed, win32 error %d\n", GetLastError ()); else small_printf ("fork_helper: WaitForMultipleObjects timed out\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), WaitForMultipleObjects failed\n"); TerminateProcess (child->hProcess, 1); goto cleanup; } else if(rc == WAIT_OBJECT_0) { /* Child died. Clean up and exit. */ DWORD errcode; GetExitCodeProcess(child->hProcess, &errcode); small_printf ("fork_helper: child died before initialization with win32 error %d\n", errcode); set_errno (EAGAIN); syscall_printf ("fork_helper: Child died before forkee_stopped signalled\n"); goto cleanup; } SuspendThread (child->hThread); /* Now fill in the stack and heap - this has to be done after the child is started. */ rc = copy_memory_to_forkee (child->hProcess, u->base, u->ptr, 2); if (rc) rc = copy_memory_to_forkee (child->hProcess, &x, u->initial_sp, 3); if (! rc) { small_printf ("fork_helper: copy of stack/heap failed\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), stack/heap copy failed\n"); TerminateProcess (child->hProcess, 1); goto cleanup; } if(copy_copyonwrite_mmaps_to_forkee(child->hProcess)) { small_printf("fork_helper: copy_copyonwrite_mmaps_to_forkee failed\n"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), copy of copyonwrite memory failed\n"); TerminateProcess (child->hProcess, 1); goto cleanup; } /* * Now we have started the child we can get rid of the * childs fd table from our address space. */ delete [] child_hinfo; /* Start the child up again. */ SetEvent (forker_stopped); ResumeThread (child->hThread); res = child->get_pid (); unlock_pinfo(); } else { /* We arrive here via a longjmp from "crt0". */ debug_printf ("fork_helper: child is running\n"); u->self = s->p[x]; reent_data = u->self->reent_save; debug_printf ("fork child: self %p, pid %d, ppid %d\n", u->self, x, u->self->ppid); if(recreate_mmaps_after_fork(u->self->mmap_ptr)) { small_printf("fork child: recreate_mmaps_after_fork_failed\n"); ExitProcess (1); } /* Tell our parent we've started. */ SetEvent (forkee_stopped); /* Wait for the parent to fill in our stack and heap. Don't wait forever here. If our parent dies we don't want to clog the system. If the wait fails, we really can't continue so exit. */ int rc = WaitForSingleObject (forker_stopped, FORK_WAIT_TIMEOUT); switch (rc) { case WAIT_ERROR_RC: small_printf ("fork child: WaitForSingleObject failed, win32 error %d\n", GetLastError ()); ExitProcess (1); case WAIT_TIMEOUT: small_printf ("fork child: WaitForSingleObject timed out\n"); ExitProcess (1); default: break; } /* Ensure winsock is enabled for the child. */ socket_checkinit(); #if 0 print_checksum (4, s->base[0][0], s->base[0][1]); print_checksum (5, s->base[1][0], s->base[1][1]); print_checksum (6, s->base[2][0], s->base[2][1]); print_checksum (7, s->base[3][0], s->base[3][1]); #endif res = 0; } syscall_printf ("%d = fork()\n", res); return res; /* Common cleanup code for failure cases */ cleanup: /* Remember to de-allocate the fd table. */ delete [] child_hinfo; child->inuse_p = PID_NOT_IN_USE; unlock_pinfo(); CloseHandle(child->hProcess); CloseHandle(child->hThread); return -1; } /* This hack uses setjmp/longjmp to ensure that the parent's registers are all available in the child. We could use GetThreadContext/SetThreadContext instead, but I'm sure this is much faster. */ static int __fork () { jmp_buf b; int r; if ((r = setjmp (b)) != 0) { r = r == -2 ? -1 : r == -1 ? 0 : r; return r; } r = cygwin_fork_helper1 (u->data_start, u->data_end, u->bss_start, u->bss_end); /* Must convert result to get it through setjmp ok. */ longjmp (b, r == -1 ? -2 : r == 0 ? -1 : r); } /* Utility to dump a setjmp buf. */ void dump_jmp_buf (jmp_buf buf) { #ifdef __i386__ debug_printf ("jmp_buf: eax 0x%x, ebx 0x%x, ecx 0x%x, edx 0x%x\n", buf[0], buf[1], buf[2], buf[3]); debug_printf ("jmp_buf: esi 0x%x, edi 0x%x, ebp 0x%x, esp 0x%x\n", buf[4], buf[5], buf[6], buf[7]); short *s = (short *) &buf[9]; debug_printf ("jmp_buf: es 0x%x, fs 0x%x, gs 0x%x, ss 0x%x\n", s[0], s[1], s[2], s[3]); debug_printf ("jmp_buf: eip: 0x%x\n", buf[8]); #endif } extern "C" int vfork () { return fork (); } extern "C" int fork () { /* FIXME: Was there a real reason for this? */ alloca (100); int r = __fork (); return r; }