From 41431ad5531eff99c85ea598a2fe1c3e8c5c8329 Mon Sep 17 00:00:00 2001 From: Christopher Faylor Date: Tue, 16 Nov 2004 06:02:05 +0000 Subject: experimental branch which removes cygwin's reparenting code, in favor of a pipe. --- winsup/cygwin/child_info.h | 110 ++++ winsup/cygwin/cygthread.cc | 313 ++++++++++ winsup/cygwin/dcrt0.cc | 1177 +++++++++++++++++++++++++++++++++++ winsup/cygwin/dtable.cc | 910 +++++++++++++++++++++++++++ winsup/cygwin/exceptions.cc | 1206 ++++++++++++++++++++++++++++++++++++ winsup/cygwin/fork.cc | 740 ++++++++++++++++++++++ winsup/cygwin/include/sys/cygwin.h | 265 ++++++++ winsup/cygwin/include/sys/wait.h | 74 +++ winsup/cygwin/pinfo.cc | 981 +++++++++++++++++++++++++++++ winsup/cygwin/pinfo.h | 226 +++++++ winsup/cygwin/signal.cc | 551 ++++++++++++++++ winsup/cygwin/sigproc.cc | 1045 +++++++++++++++++++++++++++++++ winsup/cygwin/sigproc.h | 97 +++ winsup/cygwin/spawn.cc | 1088 ++++++++++++++++++++++++++++++++ winsup/cygwin/tty.cc | 494 +++++++++++++++ 15 files changed, 9277 insertions(+) create mode 100644 winsup/cygwin/child_info.h create mode 100644 winsup/cygwin/cygthread.cc create mode 100644 winsup/cygwin/dcrt0.cc create mode 100644 winsup/cygwin/dtable.cc create mode 100644 winsup/cygwin/exceptions.cc create mode 100644 winsup/cygwin/fork.cc create mode 100644 winsup/cygwin/include/sys/cygwin.h create mode 100644 winsup/cygwin/include/sys/wait.h create mode 100644 winsup/cygwin/pinfo.cc create mode 100644 winsup/cygwin/pinfo.h create mode 100644 winsup/cygwin/signal.cc create mode 100644 winsup/cygwin/sigproc.cc create mode 100644 winsup/cygwin/sigproc.h create mode 100644 winsup/cygwin/spawn.cc create mode 100644 winsup/cygwin/tty.cc diff --git a/winsup/cygwin/child_info.h b/winsup/cygwin/child_info.h new file mode 100644 index 00000000000..e3d643a4f96 --- /dev/null +++ b/winsup/cygwin/child_info.h @@ -0,0 +1,110 @@ +/* child_info.h: shared child info for cygwin + + Copyright 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include + +enum +{ + _PROC_EXEC, + _PROC_SPAWN, + _PROC_FORK, + _PROC_WHOOPS +}; + +#define OPROC_MAGIC_MASK 0xff00ff00 +#define OPROC_MAGIC_GENERIC 0xaf00f000 + +#define PROC_MAGIC_GENERIC 0xaf00fa00 + +#define PROC_EXEC (_PROC_EXEC) +#define PROC_SPAWN (_PROC_SPAWN) +#define PROC_FORK (_PROC_FORK) + +#define EXEC_MAGIC_SIZE sizeof(child_info) + +#define CURR_CHILD_INFO_MAGIC 0x568a5527U + +/* NOTE: Do not make gratuitous changes to the names or organization of the + below class. The layout is checksummed to determine compatibility between + different cygwin versions. */ +class child_info +{ +public: + DWORD zero[4]; // must be zeroed + DWORD cb; // size of this record + DWORD intro; // improbable string + unsigned long magic; // magic number unique to child_info + unsigned short type; // type of record, exec, spawn, fork + HANDLE subproc_ready; // used for synchronization with parent + HANDLE user_h; + HANDLE parent; + init_cygheap *cygheap; + void *cygheap_max; + DWORD cygheap_reserve_sz; + HANDLE cygheap_h; + unsigned fhandler_union_cb; +}; + +class mount_info; +class _pinfo; + +class child_info_fork: public child_info +{ +public: + HANDLE forker_finished;// for synchronization with child + DWORD stacksize; // size of parent stack + jmp_buf jmp; // where child will jump to + void *stacktop; // location of top of parent stack + void *stackbottom; // location of bottom of parent stack +}; + +class fhandler_base; + +class cygheap_exec_info +{ +public: + char *old_title; + int argc; + char **argv; + int envc; + char **envp; + HANDLE myself_pinfo; +}; + +class child_info_spawn: public child_info +{ +public: + cygheap_exec_info *moreinfo; + HANDLE hexec_proc; + + child_info_spawn (): moreinfo (NULL) {} + ~child_info_spawn () + { + if (moreinfo) + { + if (moreinfo->old_title) + cfree (moreinfo->old_title); + if (moreinfo->envp) + { + for (char **e = moreinfo->envp; *e; e++) + cfree (*e); + cfree (moreinfo->envp); + } + CloseHandle (moreinfo->myself_pinfo); + cfree (moreinfo); + } + } +}; + +void __stdcall init_child_info (DWORD, child_info *, HANDLE); + +extern child_info *child_proc_info; +extern child_info_spawn *spawn_info __attribute__ ((alias ("child_proc_info"))); +extern child_info_fork *fork_info __attribute__ ((alias ("child_proc_info"))); diff --git a/winsup/cygwin/cygthread.cc b/winsup/cygwin/cygthread.cc new file mode 100644 index 00000000000..e3f3fdc4ccf --- /dev/null +++ b/winsup/cygwin/cygthread.cc @@ -0,0 +1,313 @@ +/* cygthread.cc + + Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include "exceptions.h" +#include "security.h" +#include "cygthread.h" +#include "sync.h" +#include "cygerrno.h" +#include "sigproc.h" +#include "thread.h" +#include "cygtls.h" + +#undef CloseHandle + +static cygthread NO_COPY threads[128]; +#define NTHREADS (sizeof (threads) / sizeof (threads[0])) + +DWORD NO_COPY cygthread::main_thread_id; +bool NO_COPY cygthread::exiting; + +/* Initial stub called by cygthread constructor. Performs initial + per-thread initialization and loops waiting for new thread functions + to execute. */ +DWORD WINAPI +cygthread::stub (VOID *arg) +{ + cygthread *info = (cygthread *) arg; + if (info->arg == cygself) + { + if (info->ev) + { + CloseHandle (info->ev); + CloseHandle (info->thread_sync); + } + info->ev = info->thread_sync = info->stack_ptr = NULL; + } + else + { + info->stack_ptr = &arg; + if (!info->ev) + { + info->ev = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); + info->thread_sync = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + } + } + + while (1) + { + if (!info->__name) + system_printf ("erroneous thread activation"); + else + { + if (!info->func || exiting) + return 0; + + /* Cygwin threads should not call ExitThread directly */ + info->func (info->arg == cygself ? info : info->arg); + /* ...so the above should always return */ + +#ifdef DEBUGGING + info->func = NULL; // catch erroneous activation +#endif + info->__name = NULL; + SetEvent (info->ev); + } + switch (WaitForSingleObject (info->thread_sync, INFINITE)) + { + case WAIT_OBJECT_0: + continue; + default: + api_fatal ("WFSO failed, %E"); + break; + } + } +} + +/* Overflow stub called by cygthread constructor. Calls specified function + and then exits the thread. */ +DWORD WINAPI +cygthread::simplestub (VOID *arg) +{ + cygthread *info = (cygthread *) arg; + info->stack_ptr = &arg; + info->ev = info->h; + info->func (info->arg == cygself ? info : info->arg); + return 0; +} + +/* Start things going. Called from dll_crt0_1. */ +void +cygthread::init () +{ + main_thread_id = GetCurrentThreadId (); +} + +cygthread * +cygthread::freerange () +{ + cygthread *self = (cygthread *) calloc (1, sizeof (*self)); + self->is_freerange = true; + self->inuse = 1; + return self; +} + +void * cygthread::operator +new (size_t) +{ + cygthread *info; + + /* Search the threads array for an empty slot to use */ + for (info = threads; info < threads + NTHREADS; info++) + if (!InterlockedExchange (&info->inuse, 1)) + { + /* available */ +#ifdef DEBUGGING + if (info->__name) + api_fatal ("name not NULL? id %p, i %d", info->id, info - threads); +#endif + goto out; + } + +#ifdef DEBUGGING + char buf[1024]; + if (!GetEnvironmentVariable ("CYGWIN_FREERANGE_NOCHECK", buf, sizeof (buf))) + api_fatal ("Overflowed cygwin thread pool"); + else + thread_printf ("Overflowed cygwin thread pool"); +#endif + + info = freerange (); /* exhausted thread pool */ + +out: + return info; +} + +cygthread::cygthread (LPTHREAD_START_ROUTINE start, LPVOID param, + const char *name): __name (name), + func (start), arg (param) +{ + thread_printf ("name %s, id %p", name, id); + if (h) + { + while (!thread_sync) + low_priority_sleep (0); + SetEvent (thread_sync); + thread_printf ("activated thread_sync %p", thread_sync); + } + else + { + stack_ptr = NULL; + h = CreateThread (&sec_none_nih, 0, is_freerange ? simplestub : stub, + this, 0, &id); + if (!h) + api_fatal ("thread handle not set - %p<%p>, %E", h, id); + thread_printf ("created thread %p", h); + } +} + +/* Return the symbolic name of the current thread for debugging. + */ +const char * +cygthread::name (DWORD tid) +{ + const char *res = NULL; + if (!tid) + tid = GetCurrentThreadId (); + + if (tid == main_thread_id) + return "main"; + + for (DWORD i = 0; i < NTHREADS; i++) + if (threads[i].id == tid) + { + res = threads[i].__name ?: "exiting thread"; + break; + } + + if (!res) + { + static char buf[30] NO_COPY = {0}; + __small_sprintf (buf, "unknown (%p)", tid); + res = buf; + } + + return res; +} + +cygthread::operator +HANDLE () +{ + while (!ev) + low_priority_sleep (0); + return ev; +} + +/* Should only be called when the process is exiting since it + leaves an open thread slot. */ +void +cygthread::exit_thread () +{ + if (!is_freerange) + SetEvent (*this); + ExitThread (0); +} + +/* Forcibly terminate a thread. */ +void +cygthread::terminate_thread () +{ + if (!is_freerange) + { + ResetEvent (*this); + ResetEvent (thread_sync); + } + (void) TerminateThread (h, 0); + (void) WaitForSingleObject (h, INFINITE); + CloseHandle (h); + + while (!stack_ptr) + low_priority_sleep (0); + + MEMORY_BASIC_INFORMATION m; + memset (&m, 0, sizeof (m)); + (void) VirtualQuery (stack_ptr, &m, sizeof m); + + if (!m.RegionSize) + system_printf ("m.RegionSize 0? stack_ptr %p", stack_ptr); + else if (!VirtualFree (m.AllocationBase, 0, MEM_RELEASE)) + debug_printf ("VirtualFree of allocation base %p<%p> failed, %E", + stack_ptr, m.AllocationBase); + + if (is_freerange) + free (this); + else + { + h = NULL; + __name = NULL; + stack_ptr = NULL; + (void) InterlockedExchange (&inuse, 0); /* No longer in use */ + } +} + +/* Detach the cygthread from the current thread. Note that the + theory is that cygthreads are only associated with one thread. + So, there should be never be multiple threads doing waits + on the same cygthread. */ +bool +cygthread::detach (HANDLE sigwait) +{ + bool signalled = false; + if (!inuse) + system_printf ("called detach but inuse %d, thread %p?", inuse, id); + else + { + DWORD res; + + if (!sigwait) + res = WaitForSingleObject (*this, INFINITE); + else + { + HANDLE w4[2]; + w4[0] = *this; + w4[1] = signal_arrived; + res = WaitForSingleObject (sigwait, INFINITE); + if (res != WAIT_OBJECT_0) + system_printf ("WFSO sigwait %p failed, res %u, %E", sigwait, res); + res = WaitForMultipleObjects (2, w4, FALSE, INFINITE); + if (res == WAIT_OBJECT_0) + /* nothing */; + else if (WaitForSingleObject (sigwait, 0) == WAIT_OBJECT_0) + res = WaitForSingleObject (*this, INFINITE); + else if ((res = WaitForSingleObject (*this, 0)) != WAIT_OBJECT_0) + { + signalled = true; + terminate_thread (); + set_sig_errno (EINTR); /* caller should be dealing with return + values. */ + } + } + + thread_printf ("%s returns %d, id %p", sigwait ? "WFMO" : "WFSO", + res, id); + + if (signalled) + /* already handled */; + else if (is_freerange) + { + CloseHandle (h); + free (this); + } + else + { + ResetEvent (*this); + /* Mark the thread as available by setting inuse to zero */ + (void) InterlockedExchange (&inuse, 0); + } + } + return signalled; +} + +void +cygthread::terminate () +{ + exiting = 1; +} diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc new file mode 100644 index 00000000000..eabda1c8968 --- /dev/null +++ b/winsup/cygwin/dcrt0.cc @@ -0,0 +1,1177 @@ +/* dcrt0.cc -- essentially the main() for the Cygwin dll + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include "glob.h" +#include "exceptions.h" +#include +#include +#include +#include +#include "sigproc.h" +#include "pinfo.h" +#include "cygerrno.h" +#define NEED_VFORK +#include "perprocess.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "child_info_magic.h" +#include "perthread.h" +#include "shared_info.h" +#include "cygwin_version.h" +#include "dll_init.h" +#include "cygthread.h" +#include "sync.h" +#include "heap.h" + +#define MAX_AT_FILE_LEVEL 10 + +#define PREMAIN_LEN (sizeof (user_data->premain) / sizeof (user_data->premain[0])) + +HANDLE NO_COPY hMainProc = (HANDLE) -1; +HANDLE NO_COPY hMainThread; + +#ifdef NEWVFORK +per_thread_vfork NO_COPY vfork_storage; +#endif + +per_thread NO_COPY *threadstuff[] = { +#ifdef NEWVFORK + &vfork_storage, +#endif + NULL}; + +bool display_title; +bool strip_title_path; +bool allow_glob = true; +codepage_type current_codepage = ansi_cp; + +int __argc_safe; +int _declspec(dllexport) __argc; +char _declspec(dllexport) **__argv; +#ifdef NEWVFORK +vfork_save NO_COPY *main_vfork; +#endif + +static int NO_COPY envc; +char NO_COPY **envp; + +extern "C" void __sinit (_reent *); + +_cygtls NO_COPY *_main_tls; + +bool NO_COPY cygwin_finished_initializing; + +/* Used in SIGTOMASK for generating a bit for insertion into a sigset_t. + This is subtracted from the signal number prior to shifting the bit. + In older versions of cygwin, the signal was used as-is to shift the + bit for masking. So, we'll temporarily detect this and set it to zero + for programs that are linked using older cygwins. This is just a stopgap + measure to allow an orderly transfer to the new, correct sigmask method. */ +unsigned NO_COPY int signal_shift_subtract = 1; + +ResourceLocks _reslock NO_COPY; +MTinterface _mtinterf; + +bool NO_COPY _cygwin_testing; + +char NO_COPY almost_null[1]; + +extern "C" +{ + /* This is an exported copy of environ which can be used by DLLs + which use cygwin.dll. */ + char **__cygwin_environ; + char ***main_environ; + /* __progname used in getopt error message */ + char *__progname; + struct per_process __cygwin_user_data = + {/* initial_sp */ 0, /* magic_biscuit */ 0, + /* dll_major */ CYGWIN_VERSION_DLL_MAJOR, + /* dll_major */ CYGWIN_VERSION_DLL_MINOR, + /* impure_ptr_ptr */ NULL, /* envptr */ NULL, + /* malloc */ malloc, /* free */ free, + /* realloc */ realloc, + /* fmode_ptr */ NULL, /* main */ NULL, /* ctors */ NULL, + /* dtors */ NULL, /* data_start */ NULL, /* data_end */ NULL, + /* bss_start */ NULL, /* bss_end */ NULL, + /* calloc */ calloc, + /* premain */ {NULL, NULL, NULL, NULL}, + /* run_ctors_p */ 0, + /* unused */ {0, 0, 0, 0, 0, 0, 0}, + /* forkee */ 0, + /* hmodule */ NULL, + /* api_major */ CYGWIN_VERSION_API_MAJOR, + /* api_minor */ CYGWIN_VERSION_API_MINOR, + /* unused2 */ {0, 0, 0, 0, 0}, + /* resourcelocks */ &_reslock, /* threadinterface */ &_mtinterf, + /* impure_ptr */ _GLOBAL_REENT, + }; + bool ignore_case_with_glob; + int __declspec (dllexport) _check_for_executable = true; +#ifdef DEBUGGING + int pinger; +#endif +}; + +char *old_title; +char title_buf[TITLESIZE + 1]; + +static void +do_global_dtors (void) +{ + if (user_data->dtors) + { + void (**pfunc)() = user_data->dtors; + while (*++pfunc) + (*pfunc) (); + } +} + +static void __stdcall +do_global_ctors (void (**in_pfunc)(), int force) +{ + if (!force && user_data->forkee) + return; // inherit constructed stuff from parent pid + + /* Run ctors backwards, so skip the first entry and find how many + there are, then run them. */ + + void (**pfunc) () = in_pfunc; + + while (*++pfunc) + ; + while (--pfunc > in_pfunc) + (*pfunc) (); +} + +/* + * Replaces @file in the command line with the contents of the file. + * There may be multiple @file's in a single command line + * A \@file is replaced with @file so that echo \@foo would print + * @foo and not the contents of foo. + */ +static bool __stdcall +insert_file (char *name, char *&cmd) +{ + HANDLE f; + DWORD size; + + f = CreateFile (name + 1, + GENERIC_READ, /* open for reading */ + FILE_SHARE_READ, /* share for reading */ + &sec_none_nih, /* no security */ + OPEN_EXISTING, /* existing file only */ + FILE_ATTRIBUTE_NORMAL, /* normal file */ + NULL); /* no attr. template */ + + if (f == INVALID_HANDLE_VALUE) + { + debug_printf ("couldn't open file '%s', %E", name); + return false; + } + + /* This only supports files up to about 4 billion bytes in + size. I am making the bold assumption that this is big + enough for this feature */ + size = GetFileSize (f, NULL); + if (size == 0xFFFFFFFF) + { + debug_printf ("couldn't get file size for '%s', %E", name); + return false; + } + + int new_size = strlen (cmd) + size + 2; + char *tmp = (char *) malloc (new_size); + if (!tmp) + { + debug_printf ("malloc failed, %E"); + return false; + } + + /* realloc passed as it should */ + DWORD rf_read; + BOOL rf_result; + rf_result = ReadFile (f, tmp, size, &rf_read, NULL); + CloseHandle (f); + if (!rf_result || (rf_read != size)) + { + debug_printf ("ReadFile failed, %E"); + return false; + } + + tmp[size++] = ' '; + strcpy (tmp + size, cmd); + cmd = tmp; + return true; +} + +static inline int +isquote (char c) +{ + char ch = c; + return ch == '"' || ch == '\''; +} + +/* Step over a run of characters delimited by quotes */ +static /*__inline*/ char * +quoted (char *cmd, int winshell) +{ + char *p; + char quote = *cmd; + + if (!winshell) + { + char *p; + strcpy (cmd, cmd + 1); + if (*(p = strechr (cmd, quote))) + strcpy (p, p + 1); + return p; + } + + const char *s = quote == '\'' ? "'" : "\\\""; + /* This must have been run from a Windows shell, so preserve + quotes for globify to play with later. */ + while (*cmd && *++cmd) + if ((p = strpbrk (cmd, s)) == NULL) + { + cmd = strchr (cmd, '\0'); // no closing quote + break; + } + else if (*p == '\\') + cmd = ++p; + else if (quote == '"' && p[1] == '"') + { + *p = '\\'; + cmd = ++p; // a quoted quote + } + else + { + cmd = p + 1; // point to after end + break; + } + return cmd; +} + +/* Perform a glob on word if it contains wildcard characters. + Also quote every character between quotes to force glob to + treat the characters literally. */ +static int __stdcall +globify (char *word, char **&argv, int &argc, int &argvlen) +{ + if (*word != '~' && strpbrk (word, "?*[\"\'(){}") == NULL) + return 0; + + int n = 0; + char *p, *s; + int dos_spec = isdrive (word); + if (!dos_spec && isquote (*word) && word[1] && word[2]) + dos_spec = isdrive (word + 1); + + /* We'll need more space if there are quoting characters in + word. If that is the case, doubling the size of the + string should provide more than enough space. */ + if (strpbrk (word, "'\"")) + n = strlen (word); + char pattern[strlen (word) + ((dos_spec + 1) * n) + 1]; + + /* Fill pattern with characters from word, quoting any + characters found within quotes. */ + for (p = pattern, s = word; *s != '\000'; s++, p++) + if (!isquote (*s)) + { + if (dos_spec && *s == '\\') + *p++ = '\\'; + *p = *s; + } + else + { + char quote = *s; + while (*++s && *s != quote) + { + if (dos_spec || *s != '\\') + /* nothing */; + else if (s[1] == quote || s[1] == '\\') + s++; + *p++ = '\\'; + *p++ = *s; + } + if (*s == quote) + p--; + if (*s == '\0') + break; + } + + *p = '\0'; + + glob_t gl; + gl.gl_offs = 0; + + /* Attempt to match the argument. Return just word (minus quoting) if no match. */ + if (glob (pattern, GLOB_TILDE | GLOB_NOCHECK | GLOB_BRACE | GLOB_QUOTE, NULL, &gl) || !gl.gl_pathc) + return 0; + + /* Allocate enough space in argv for the matched filenames. */ + n = argc; + if ((argc += gl.gl_pathc) > argvlen) + { + argvlen = argc + 10; + argv = (char **) realloc (argv, (1 + argvlen) * sizeof (argv[0])); + } + + /* Copy the matched filenames to argv. */ + char **gv = gl.gl_pathv; + char **av = argv + n; + while (*gv) + { + debug_printf ("argv[%d] = '%s'", n++, *gv); + *av++ = *gv++; + } + + /* Clean up after glob. */ + free (gl.gl_pathv); + return 1; +} + +/* Build argv, argc from string passed from Windows. */ + +static void __stdcall +build_argv (char *cmd, char **&argv, int &argc, int winshell) +{ + int argvlen = 0; + int nesting = 0; // monitor "nesting" from insert_file + + argc = 0; + argvlen = 0; + argv = NULL; + + /* Scan command line until there is nothing left. */ + while (*cmd) + { + /* Ignore spaces */ + if (issep (*cmd)) + { + cmd++; + continue; + } + + /* Found the beginning of an argument. */ + char *word = cmd; + char *sawquote = NULL; + while (*cmd) + { + if (*cmd != '"' && (!winshell || *cmd != '\'')) + cmd++; // Skip over this character + else + /* Skip over characters until the closing quote */ + { + sawquote = cmd; + cmd = quoted (cmd, winshell && argc > 0); + } + if (issep (*cmd)) // End of argument if space + break; + } + if (*cmd) + *cmd++ = '\0'; // Terminate `word' + + /* Possibly look for @file construction assuming that this isn't + the very first argument and the @ wasn't quoted */ + if (argc && sawquote != word && *word == '@') + { + if (++nesting > MAX_AT_FILE_LEVEL) + api_fatal ("Too many levels of nesting for %s", word); + if (insert_file (word, cmd)) + continue; // There's new stuff in cmd now + } + + /* See if we need to allocate more space for argv */ + if (argc >= argvlen) + { + argvlen = argc + 10; + argv = (char **) realloc (argv, (1 + argvlen) * sizeof (argv[0])); + } + + /* Add word to argv file after (optional) wildcard expansion. */ + if (!winshell || !argc || !globify (word, argv, argc, argvlen)) + { + debug_printf ("argv[%d] = '%s'", argc, word); + argv[argc++] = word; + } + } + + argv[argc] = NULL; + + debug_printf ("argc %d", argc); +} + +/* sanity and sync check */ +void __stdcall +check_sanity_and_sync (per_process *p) +{ + /* Sanity check to make sure developers didn't change the per_process */ + /* struct without updating SIZEOF_PER_PROCESS [it makes them think twice */ + /* about changing it]. */ + if (sizeof (per_process) != SIZEOF_PER_PROCESS) + { + api_fatal ("per_process sanity check failed"); + } + + /* Make sure that the app and the dll are in sync. */ + + /* Complain if older than last incompatible change */ + if (p->dll_major < CYGWIN_VERSION_DLL_EPOCH) + api_fatal ("cygwin DLL and APP are out of sync -- DLL version mismatch %d < %d", + p->dll_major, CYGWIN_VERSION_DLL_EPOCH); + + /* magic_biscuit != 0 if using the old style version numbering scheme. */ + if (p->magic_biscuit != SIZEOF_PER_PROCESS) + api_fatal ("Incompatible cygwin .dll -- incompatible per_process info %d != %d", + p->magic_biscuit, SIZEOF_PER_PROCESS); + + /* Complain if incompatible API changes made */ + if (p->api_major > cygwin_version.api_major) + api_fatal ("cygwin DLL and APP are out of sync -- API version mismatch %d > %d", + p->api_major, cygwin_version.api_major); + + if (CYGWIN_VERSION_DLL_MAKE_COMBINED (p->dll_major, p->dll_minor) <= + CYGWIN_VERSION_DLL_BAD_SIGNAL_MASK) + signal_shift_subtract = 0; +} + +child_info NO_COPY *child_proc_info = NULL; +static MEMORY_BASIC_INFORMATION NO_COPY sm; + +#define CYGWIN_GUARD ((wincap.has_page_guard ()) ? \ + PAGE_EXECUTE_READWRITE|PAGE_GUARD : PAGE_NOACCESS) + +static void +alloc_stack_hard_way (child_info_fork *ci, volatile char *b) +{ + void *new_stack_pointer; + MEMORY_BASIC_INFORMATION m; + void *newbase; + int newlen; + LPBYTE curbot = (LPBYTE) sm.BaseAddress + sm.RegionSize; + bool noguard; + + if (ci->stacktop > (LPBYTE) sm.AllocationBase && ci->stacktop < curbot) + { + newbase = curbot; + newlen = (LPBYTE) ci->stackbottom - (LPBYTE) curbot; + noguard = 1; + } + else + { + newbase = ci->stacktop; + newlen = (DWORD) ci->stackbottom - (DWORD) ci->stacktop; + noguard = 0; + } + if (!VirtualAlloc (newbase, newlen, MEM_RESERVE, PAGE_NOACCESS)) + api_fatal ("fork: can't reserve memory for stack %p - %p, %E", + ci->stacktop, ci->stackbottom); + + new_stack_pointer = (void *) ((LPBYTE) ci->stackbottom - ci->stacksize); + + if (!VirtualAlloc (new_stack_pointer, ci->stacksize, MEM_COMMIT, + PAGE_EXECUTE_READWRITE)) + api_fatal ("fork: can't commit memory for stack %p(%d), %E", + new_stack_pointer, ci->stacksize); + if (!VirtualQuery ((LPCVOID) new_stack_pointer, &m, sizeof m)) + api_fatal ("fork: couldn't get new stack info, %E"); + if (!noguard) + { + m.BaseAddress = (LPVOID) ((DWORD) m.BaseAddress - 1); + if (!VirtualAlloc ((LPVOID) m.BaseAddress, 1, MEM_COMMIT, + CYGWIN_GUARD)) + api_fatal ("fork: couldn't allocate new stack guard page %p, %E", + m.BaseAddress); + } + if (!VirtualQuery ((LPCVOID) m.BaseAddress, &m, sizeof m)) + api_fatal ("fork: couldn't get new stack info, %E"); + ci->stacktop = m.BaseAddress; + b[0] = '\0'; +} + +/* extend the stack prior to fork longjmp */ + +static void +alloc_stack (child_info_fork *ci) +{ + /* FIXME: adding 16384 seems to avoid a stack copy problem during + fork on Win95, but I don't know exactly why yet. DJ */ + volatile char b[ci->stacksize + 16384]; + + if (!VirtualQuery ((LPCVOID) &b, &sm, sizeof sm)) + api_fatal ("fork: couldn't get stack info, %E"); + + if (sm.AllocationBase == ci->stacktop) + ci->stacksize = 0; + else + alloc_stack_hard_way (ci, b + sizeof (b) - 1); + + return; +} + +#ifdef DEBUGGING +void +break_here () +{ + debug_printf ("break here"); +} +#endif + +static void +initial_env () +{ + char buf[CYG_MAX_PATH + 1]; + if (GetEnvironmentVariable ("CYGWIN_TESTING", buf, sizeof (buf) - 1)) + _cygwin_testing = 1; + +#ifdef DEBUGGING + DWORD len; + + if (GetEnvironmentVariable ("CYGWIN_SLEEP", buf, sizeof (buf) - 1)) + { + DWORD ms = atoi (buf); + buf[0] = '\0'; + len = GetModuleFileName (NULL, buf, CYG_MAX_PATH); + console_printf ("Sleeping %d, pid %u %s\n", ms, GetCurrentProcessId (), buf); + Sleep (ms); + if (!strace.active) + { + strace.inited = 0; + strace.hello (); + } + } + if (GetEnvironmentVariable ("CYGWIN_DEBUG", buf, sizeof (buf) - 1)) + { + char buf1[CYG_MAX_PATH + 1]; + len = GetModuleFileName (NULL, buf1, CYG_MAX_PATH); + strlwr (buf1); + strlwr (buf); + char *p = strchr (buf, ':'); + if (!p) + p = (char *) "gdb.exe -nw"; + else + *p++ = '\0'; + if (strstr (buf1, buf)) + { + error_start_init (p); + try_to_debug (); + console_printf ("*** Sending Break. gdb may issue spurious SIGTRAP message.\n"); + DebugBreak (); + break_here (); + } + } +#endif + +} + +void __stdcall +dll_crt0_0 () +{ + wincap.init (); + initial_env (); + + char zeros[sizeof (child_proc_info->zero)] = {0}; + + init_console_handler (); + init_global_security (); + if (!DuplicateHandle (GetCurrentProcess (), GetCurrentProcess (), + GetCurrentProcess (), &hMainProc, 0, FALSE, + DUPLICATE_SAME_ACCESS)) + hMainProc = GetCurrentProcess (); + + DuplicateHandle (hMainProc, GetCurrentThread (), hMainProc, + &hMainThread, 0, false, DUPLICATE_SAME_ACCESS); + + (void) SetErrorMode (SEM_FAILCRITICALERRORS); + + STARTUPINFO si; + GetStartupInfo (&si); + child_proc_info = (child_info *) si.lpReserved2; + + if (si.cbReserved2 < EXEC_MAGIC_SIZE || !child_proc_info + || memcmp (child_proc_info->zero, zeros, + sizeof (child_proc_info->zero)) != 0) + child_proc_info = NULL; + else + { + if ((child_proc_info->intro & OPROC_MAGIC_MASK) == OPROC_MAGIC_GENERIC) + multiple_cygwin_problem ("proc", child_proc_info->intro, 0); + else if (child_proc_info->intro == PROC_MAGIC_GENERIC + && child_proc_info->magic != CHILD_INFO_MAGIC) + multiple_cygwin_problem ("proc", child_proc_info->magic, + CHILD_INFO_MAGIC); + else if (child_proc_info->cygheap != (void *) &_cygheap_start) + multiple_cygwin_problem ("cygheap", (DWORD) child_proc_info->cygheap, + (DWORD) &_cygheap_start); + unsigned should_be_cb = 0; + switch (child_proc_info->type) + { + case _PROC_FORK: + user_data->forkee = true; + should_be_cb = sizeof (child_info_fork); + /* fall through */; + case _PROC_SPAWN: + case _PROC_EXEC: + if (!should_be_cb) + should_be_cb = sizeof (child_info); + if (should_be_cb != child_proc_info->cb) + multiple_cygwin_problem ("proc size", child_proc_info->cb, should_be_cb); + else if (sizeof (fhandler_union) != child_proc_info->fhandler_union_cb) + multiple_cygwin_problem ("fhandler size", child_proc_info->fhandler_union_cb, sizeof (fhandler_union)); + else + { + cygwin_user_h = child_proc_info->user_h; + break; + } + default: + system_printf ("unknown exec type %d", child_proc_info->type); + /* intentionally fall through */ + case _PROC_WHOOPS: + child_proc_info = NULL; + break; + } + } + + device::init (); + do_global_ctors (&__CTOR_LIST__, 1); + cygthread::init (); + + if (!child_proc_info) + memory_init (); + else + { + bool close_hexec_proc = false; + switch (child_proc_info->type) + { + case _PROC_FORK: + alloc_stack (fork_info); + cygheap_fixup_in_child (false); + memory_init (); + set_myself (NULL); + break; + case _PROC_SPAWN: + /* Have to delay closes until after cygheap is setup */ + close_hexec_proc = !!spawn_info->hexec_proc; + goto around; + case _PROC_EXEC: + hexec_proc = spawn_info->hexec_proc; + around: + HANDLE h; + cygheap_fixup_in_child (true); + memory_init (); + if (!spawn_info->moreinfo->myself_pinfo || + !DuplicateHandle (hMainProc, spawn_info->moreinfo->myself_pinfo, + hMainProc, &h, 0, FALSE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) + h = NULL; + set_myself (h); + __argc = spawn_info->moreinfo->argc; + __argv = spawn_info->moreinfo->argv; + envp = spawn_info->moreinfo->envp; + envc = spawn_info->moreinfo->envc; + cygheap->fdtab.fixup_after_exec (); + signal_fixup_after_exec (); + if (spawn_info->moreinfo->old_title) + { + old_title = strcpy (title_buf, spawn_info->moreinfo->old_title); + cfree (spawn_info->moreinfo->old_title); + } + break; + } + if (close_hexec_proc) + CloseHandle (spawn_info->hexec_proc); + } + + _cygtls::init (); + + /* Initialize events */ + events_init (); + + cygheap->cwd.init (); +} + +/* Take over from libc's crt0.o and start the application. Note the + various special cases when Cygwin DLL is being runtime loaded (as + opposed to being link-time loaded by Cygwin apps) from a non + cygwin app via LoadLibrary. */ +static void +dll_crt0_1 (char *) +{ + /* According to onno@stack.urc.tue.nl, the exception handler record must + be on the stack. */ + /* FIXME: Verify forked children get their exception handler set up ok. */ + exception_list cygwin_except_entry; + + check_sanity_and_sync (user_data); + malloc_init (); + + /* Initialize SIGSEGV handling, etc. */ + init_exceptions (&cygwin_except_entry); + + user_data->resourcelocks->Init (); + user_data->threadinterface->Init (); + ProtectHandle (hMainProc); + ProtectHandle (hMainThread); + + /* Initialize pthread mainthread when not forked and it is safe to call new, + otherwise it is reinitalized in fixup_after_fork */ + if (!user_data->forkee) + pthread::init_mainthread (); + +#ifdef DEBUGGING + strace.microseconds (); +#endif + + /* Initialize debug muto, if DLL is built with --enable-debugging. + Need to do this before any helper threads start. */ + debug_init (); + +#ifdef NEWVFORK + cygheap->fdtab.vfork_child_fixup (); + main_vfork = vfork_storage.create (); +#endif + + cygbench ("pre-forkee"); + if (user_data->forkee) + { + /* If we've played with the stack, stacksize != 0. That means that + fork() was invoked from other than the main thread. Make sure that + frame pointer is referencing the new stack so that the OS knows what + to do when it needs to increase the size of the stack. + + NOTE: Don't do anything that involves the stack until you've completed + this step. */ + if (fork_info->stacksize) + { + _tlsbase = (char *) fork_info->stackbottom; + _tlstop = (char *) fork_info->stacktop; + } + longjmp (fork_info->jmp, true); + } + +#ifdef DEBUGGING + { + extern void fork_init (); + fork_init (); + } +#endif + + /* Initialize our process table entry. */ + pinfo_init (envp, envc); + + if (!old_title && GetConsoleTitle (title_buf, TITLESIZE)) + old_title = title_buf; + + /* Allocate cygheap->fdtab */ + dtable_init (); + + /* Initialize user info. */ + uinfo_init (); + + /* Initialize signal/subprocess handling. */ + sigproc_init (); + + /* Connect to tty. */ + tty_init (); + + if (!__argc) + { + char *line = GetCommandLineA (); + line = strcpy ((char *) alloca (strlen (line) + 1), line); + + if (current_codepage == oem_cp) + CharToOemA (line, line); + + /* Scan the command line and build argv. Expand wildcards if not + called from another cygwin process. */ + build_argv (line, __argv, __argc, + NOTSTATE (myself, PID_CYGPARENT) && allow_glob); + + /* Convert argv[0] to posix rules if it's currently blatantly + win32 style. */ + if ((strchr (__argv[0], ':')) || (strchr (__argv[0], '\\'))) + { + char *new_argv0 = (char *) malloc (CYG_MAX_PATH); + cygwin_conv_to_posix_path (__argv[0], new_argv0); + __argv[0] = (char *) realloc (new_argv0, strlen (new_argv0) + 1); + } + } + + __argc_safe = __argc; + if (user_data->premain[0]) + for (unsigned int i = 0; i < PREMAIN_LEN / 2; i++) + user_data->premain[i] (__argc, __argv, user_data); + + /* Set up standard fds in file descriptor table. */ + cygheap->fdtab.stdio_init (); + + /* Set up __progname for getopt error call. */ + if (__argv[0] && (__progname = strrchr (__argv[0], '/'))) + ++__progname; + else + __progname = __argv[0]; + if (__progname) + { + char *cp = strchr (__progname, '\0') - 4; + if (cp > __progname && strcasematch (cp, ".exe")) + *cp = '\0'; + } + + /* Set new console title if appropriate. */ + + if (display_title && !dynamically_loaded) + { + char *cp = __progname; + if (strip_title_path) + for (char *ptr = cp; *ptr && *ptr != ' '; ptr++) + if (isdirsep (*ptr)) + cp = ptr + 1; + set_console_title (cp); + } + + cygwin_finished_initializing = true; + /* Call init of loaded dlls. */ + dlls.init (); + + /* Execute any specified "premain" functions */ + if (user_data->premain[PREMAIN_LEN / 2]) + for (unsigned int i = PREMAIN_LEN / 2; i < PREMAIN_LEN; i++) + user_data->premain[i] (__argc, __argv, user_data); + + debug_printf ("user_data->main %p", user_data->main); + + if (dynamically_loaded) + { + set_errno (0); + return; + } + + /* Disable case-insensitive globbing */ + ignore_case_with_glob = false; + + + set_errno (0); + + MALLOC_CHECK; + cygbench (__progname); + + /* Flush signals and ensure that signal thread is up and running. Can't + do this for noncygwin case since the signal thread is blocked due to + LoadLibrary serialization. */ + wait_for_sigthread (); + if (user_data->main) + exit (user_data->main (__argc, __argv, *user_data->envptr)); +} + +struct _reent * +initialize_main_tls (char *padding) +{ + if (!_main_tls) + { + _main_tls = &_my_tls; + _main_tls->init_thread (padding, NULL); + } + return &_main_tls->local_clib; +} + +/* Wrap the real one, otherwise gdb gets confused about + two symbols with the same name, but different addresses. + + UPTR is a pointer to global data that lives on the libc side of the + line [if one distinguishes the application from the dll]. */ + +extern "C" void __stdcall +_dll_crt0 () +{ + extern HANDLE sync_startup; + extern unsigned threadfunc_ix; + if (threadfunc_ix) + /* nothing to do */; + else if (!sync_startup) + system_printf ("internal error: sync_startup not called at start. Expect signal problems."); + else + { + (void) WaitForSingleObject (sync_startup, INFINITE); + CloseHandle (sync_startup); + } + + if (!threadfunc_ix) + system_printf ("internal error: couldn't determine location of thread function on stack. Expect signal problems."); + + main_environ = user_data->envptr; + *main_environ = NULL; + + char padding[CYGTLS_PADSIZE]; + _impure_ptr = _GLOBAL_REENT; + _impure_ptr->_stdin = &_impure_ptr->__sf[0]; + _impure_ptr->_stdout = &_impure_ptr->__sf[1]; + _impure_ptr->_stderr = &_impure_ptr->__sf[2]; + _impure_ptr->_current_locale = "C"; + + if (child_proc_info && child_proc_info->type == _PROC_FORK) + user_data->forkee = true; + else + __sinit (_impure_ptr); + + initialize_main_tls (padding); + dll_crt0_1 (padding); +} + +void +dll_crt0 (per_process *uptr) +{ + /* Set the local copy of the pointer into the user space. */ + if (uptr && uptr != user_data) + { + memcpy (user_data, uptr, per_process_overwrite); + *(user_data->impure_ptr_ptr) = _GLOBAL_REENT; + } + _dll_crt0 (); +} + +/* This must be called by anyone who uses LoadLibrary to load cygwin1.dll */ +extern "C" void +cygwin_dll_init () +{ + static char **envp; + static int _fmode; + + if (!DuplicateHandle (GetCurrentProcess (), GetCurrentProcess (), + GetCurrentProcess (), &hMainProc, 0, FALSE, + DUPLICATE_SAME_ACCESS)) + hMainProc = GetCurrentProcess (); + + DuplicateHandle (hMainProc, GetCurrentThread (), hMainProc, + &hMainThread, 0, FALSE, DUPLICATE_SAME_ACCESS); + user_data->magic_biscuit = sizeof (per_process); + + user_data->envptr = &envp; + user_data->fmode_ptr = &_fmode; + + dll_crt0_1 (NULL); +} + +extern "C" void +__main (void) +{ + do_global_ctors (user_data->ctors, false); + atexit (do_global_dtors); +} + +exit_states NO_COPY exit_state; +extern CRITICAL_SECTION exit_lock; + +void __stdcall +do_exit (int status) +{ + syscall_printf ("do_exit (%d), exit_state %d", status, exit_state); + +#ifdef NEWVFORK + vfork_save *vf = vfork_storage.val (); + if (vf != NULL && vf->pid < 0) + { + exit_state = ES_NOT_EXITING; + vf->restore_exit (status); + } +#endif + + EnterCriticalSection (&exit_lock); + muto::set_exiting_thread (); + if (exit_state < ES_EVENTS_TERMINATE) + { + exit_state = ES_EVENTS_TERMINATE; + events_terminate (); + } + + UINT n = (UINT) status; + if (exit_state < ES_THREADTERM) + { + exit_state = ES_THREADTERM; + cygthread::terminate (); + } + + if (exit_state < ES_SIGNAL) + { + exit_state = ES_SIGNAL; + signal (SIGCHLD, SIG_IGN); + signal (SIGHUP, SIG_IGN); + signal (SIGINT, SIG_IGN); + signal (SIGQUIT, SIG_IGN); + } + + if (exit_state < ES_CLOSEALL) + { + exit_state = ES_CLOSEALL; + close_all_files (); + } + + if (exit_state < ES_SIGPROCTERMINATE) + { + exit_state = ES_SIGPROCTERMINATE; + sigproc_terminate (); + } + + myself->stopsig = 0; + if (exit_state < ES_TITLE) + { + exit_state = ES_TITLE; + /* restore console title */ + if (old_title && display_title) + set_console_title (old_title); + } + + if (exit_state < ES_HUP_PGRP) + { + exit_state = ES_HUP_PGRP; + /* Kill orphaned children on group leader exit */ + if (myself->has_pgid_children && myself->pid == myself->pgid) + { + siginfo_t si; + si.si_signo = -SIGHUP; + si.si_code = SI_KERNEL; + si.si_pid = si.si_uid = si.si_errno = 0; + sigproc_printf ("%d == pgrp %d, send SIG{HUP,CONT} to stopped children", + myself->pid, myself->pgid); + kill_pgrp (myself->pgid, si); + } + } + + if (exit_state < ES_HUP_SID) + { + exit_state = ES_HUP_SID; + /* Kill the foreground process group on session leader exit */ + if (getpgrp () > 0 && myself->pid == myself->sid && real_tty_attached (myself)) + { + tty *tp = cygwin_shared->tty[myself->ctty]; + sigproc_printf ("%d == sid %d, send SIGHUP to children", + myself->pid, myself->sid); + + /* CGF FIXME: This can't be right. */ + if (tp->getsid () == myself->sid) + tp->kill_pgrp (SIGHUP); + } + + } + + if (exit_state < ES_TTY_TERMINATE) + { + exit_state = ES_TTY_TERMINATE; + tty_terminate (); + } + + minimal_printf ("winpid %d, exit %d", GetCurrentProcessId (), n); + myself->exit (n); +} + +static muto *atexit_lock; + +extern "C" int +cygwin_atexit (void (*function)(void)) +{ + int res; + if (!atexit_lock) + new_muto (atexit_lock); + atexit_lock->acquire (); + res = atexit (function); + atexit_lock->release (); + return res; +} + +extern "C" void +cygwin_exit (int n) +{ + if (atexit_lock) + atexit_lock->acquire (); + exit (n); +} + +extern "C" void +_exit (int n) +{ + do_exit (((DWORD) n & 0xff) << 8); +} + +extern "C" void +__api_fatal (const char *fmt, ...) +{ + char buf[4096]; + va_list ap; + + va_start (ap, fmt); + int n = __small_sprintf (buf, "%P (%u): *** ", cygwin_pid (GetCurrentProcessId ())); + __small_vsprintf (buf + n, fmt, ap); + va_end (ap); + strcat (buf, "\n"); + int len = strlen (buf); + DWORD done; + (void) WriteFile (GetStdHandle (STD_ERROR_HANDLE), buf, len, &done, 0); + + /* Make sure that the message shows up on the screen, too, since this is + a serious error. */ + if (GetFileType (GetStdHandle (STD_ERROR_HANDLE)) != FILE_TYPE_CHAR) + { + HANDLE h = CreateFile ("CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_WRITE, + &sec_none, OPEN_EXISTING, 0, 0); + if (h != INVALID_HANDLE_VALUE) + (void) WriteFile (h, buf, len, &done, 0); + } + + /* We are going down without mercy. Make sure we reset + our process_state. */ + sigproc_terminate (); +#ifdef DEBUGGING + (void) try_to_debug (); +#endif + myself->exit (1); +} + +void +multiple_cygwin_problem (const char *what, unsigned magic_version, unsigned version) +{ + if (_cygwin_testing && (strstr (what, "proc") || strstr (what, "cygheap"))) + { + child_proc_info->type = _PROC_WHOOPS; + return; + } + + char buf[1024]; + if (GetEnvironmentVariable ("CYGWIN_MISMATCH_OK", buf, sizeof (buf))) + return; + + if (CYGWIN_VERSION_MAGIC_VERSION (magic_version) == version) + system_printf ("%s magic number mismatch detected - %p/%p", what, magic_version, version); + else + api_fatal ("%s version mismatch detected - %p/%p.\n\ +You have multiple copies of cygwin1.dll on your system.\n\ +Search for cygwin1.dll using the Windows Start->Find/Search facility\n\ +and delete all but the most recent version. The most recent version *should*\n\ +reside in x:\\cygwin\\bin, where 'x' is the drive on which you have\n\ +installed the cygwin distribution.", what, magic_version, version); +} + +#ifdef DEBUGGING +void __stdcall +cygbench (const char *s) +{ + char buf[1024]; + if (GetEnvironmentVariable ("CYGWIN_BENCH", buf, sizeof (buf))) + small_printf ("%05d ***** %s : %10d\n", GetCurrentProcessId (), s, strace.microseconds ()); +} +#endif diff --git a/winsup/cygwin/dtable.cc b/winsup/cygwin/dtable.cc new file mode 100644 index 00000000000..81c84a99e2e --- /dev/null +++ b/winsup/cygwin/dtable.cc @@ -0,0 +1,910 @@ +/* dtable.cc: file descriptor support. + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#define __INSIDE_CYGWIN_NET__ + +#include "winsup.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define USE_SYS_TYPES_FD_SET +#include +#include "pinfo.h" +#include "cygerrno.h" +#include "perprocess.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "ntdll.h" +#include "tty.h" + +static const char NO_COPY unknown_file[] = "some disk file"; + +static const NO_COPY DWORD std_consts[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, + STD_ERROR_HANDLE}; + +static const char *handle_to_fn (HANDLE, char *); + +/* Set aside space for the table of fds */ +void +dtable_init () +{ + if (!cygheap->fdtab.size) + cygheap->fdtab.extend (NOFILE_INCR); + cygheap->fdtab.init_lock (); + +} + +void __stdcall +set_std_handle (int fd) +{ + if (fd == 0) + SetStdHandle (std_consts[fd], cygheap->fdtab[fd]->get_handle ()); + else if (fd <= 2) + SetStdHandle (std_consts[fd], cygheap->fdtab[fd]->get_output_handle ()); +} + +void +dtable::init_lock () +{ + new_muto (lock_cs); +} + +int +dtable::extend (int howmuch) +{ + int new_size = size + howmuch; + fhandler_base **newfds; + + if (howmuch <= 0) + return 0; + + if (new_size > (100 * NOFILE_INCR)) + { + set_errno (EMFILE); + return 0; + } + + /* Try to allocate more space for fd table. We can't call realloc () + here to preserve old table if memory allocation fails */ + + if (!(newfds = (fhandler_base **) ccalloc (HEAP_ARGV, new_size, sizeof newfds[0]))) + { + debug_printf ("calloc failed"); + set_errno (ENOMEM); + return 0; + } + if (fds) + { + memcpy (newfds, fds, size * sizeof (fds[0])); + cfree (fds); + } + + size = new_size; + fds = newfds; + debug_printf ("size %d, fds %p", size, fds); + return 1; +} + +void +dtable::get_debugger_info () +{ + if (being_debugged ()) + { + char std[3][sizeof ("/dev/ttyNNNN")]; + std[0][0] = std[1][0] = std [2][0] = '\0'; + char buf[sizeof ("cYgstd %x") + 32]; + sprintf (buf, "cYgstd %x %x %x", (unsigned) &std, sizeof (std[0]), 3); + OutputDebugString (buf); + for (int i = 0; i < 3; i++) + if (std[i][0]) + { + HANDLE h = GetStdHandle (std_consts[i]); + fhandler_base *fh = build_fh_name (std[i]); + if (!fh) + continue; + fds[i] = fh; + if (!fh->open ((i ? (i == 2 ? O_RDWR : O_WRONLY) : O_RDONLY) + | O_BINARY, 0777)) + release (i); + else + CloseHandle (h); + } + } +} + +/* Initialize the file descriptor/handle mapping table. + This function should only be called when a cygwin function is invoked + by a non-cygwin function, i.e., it should only happen very rarely. */ + +void +dtable::stdio_init () +{ + extern void set_console_ctty (); + /* Set these before trying to output anything from strace. + Also, always set them even if we're to pick up our parent's fds + in case they're missed. */ + + if (myself->cygstarted || ISSTATE (myself, PID_CYGPARENT)) + return; + + HANDLE in = GetStdHandle (STD_INPUT_HANDLE); + HANDLE out = GetStdHandle (STD_OUTPUT_HANDLE); + HANDLE err = GetStdHandle (STD_ERROR_HANDLE); + + init_std_file_from_handle (0, in); + + /* STD_ERROR_HANDLE has been observed to be the same as + STD_OUTPUT_HANDLE. We need separate handles (e.g. using pipes + to pass data from child to parent). */ + if (out == err) + { + /* Since this code is not invoked for forked tasks, we don't have + to worry about the close-on-exec flag here. */ + if (!DuplicateHandle (hMainProc, out, hMainProc, &err, 0, + 1, DUPLICATE_SAME_ACCESS)) + { + /* If that fails, do this as a fall back. */ + err = out; + system_printf ("couldn't make stderr distinct from stdout"); + } + } + + init_std_file_from_handle (1, out); + init_std_file_from_handle (2, err); + /* Assign the console as the controlling tty for this process if we actually + have a console and no other controlling tty has been assigned. */ + if (myself->ctty < 0 && GetConsoleCP () > 0) + set_console_ctty (); +} + +const int dtable::initial_archetype_size; + +fhandler_base * +dtable::find_archetype (device& dev) +{ + for (unsigned i = 0; i < farchetype; i++) + if (archetypes[i]->get_device () == (unsigned) dev) + return archetypes[i]; + return NULL; +} + +fhandler_base ** +dtable::add_archetype () +{ + if (farchetype++ >= narchetypes) + archetypes = (fhandler_base **) crealloc (archetypes, (narchetypes += initial_archetype_size) * sizeof archetypes[0]); + return archetypes + farchetype - 1; +} + +void +dtable::delete_archetype (fhandler_base *fh) +{ + for (unsigned i = 0; i < farchetype; i++) + if (fh == archetypes[i]) + { + debug_printf ("deleting element %d for %s", i, fh->get_name ()); + if (i < --farchetype) + archetypes[i] = archetypes[farchetype]; + break; + } + + delete fh; +} + +int +dtable::find_unused_handle (int start) +{ + do + { + for (size_t i = start; i < size; i++) + /* See if open -- no need for overhead of not_open */ + if (fds[i] == NULL) + return i; + } + while (extend (NOFILE_INCR)); + return -1; +} + +void +dtable::release (int fd) +{ + if (!not_open (fd)) + { + if (fds[fd]->need_fixup_before ()) + dec_need_fixup_before (); + fhandler_base *arch = fds[fd]->archetype; + delete fds[fd]; + if (arch && !arch->usecount) + cygheap->fdtab.delete_archetype (arch); + fds[fd] = NULL; + } +} + +extern "C" int +cygwin_attach_handle_to_fd (char *name, int fd, HANDLE handle, mode_t bin, + DWORD myaccess) +{ + if (fd == -1) + fd = cygheap->fdtab.find_unused_handle (); + fhandler_base *fh = build_fh_name (name); + cygheap->fdtab[fd] = fh; + fh->init (handle, myaccess, bin ?: fh->pc_binmode ()); + return fd; +} + +void +dtable::init_std_file_from_handle (int fd, HANDLE handle) +{ + const char *name = NULL; + CONSOLE_SCREEN_BUFFER_INFO buf; + struct sockaddr sa; + int sal = sizeof (sa); + DCB dcb; + unsigned bin = O_BINARY; + device dev; + + dev.devn = 0; /* FIXME: device */ + first_fd_for_open = 0; + + if (!not_open (fd)) + return; + + SetLastError (0); + DWORD ft = GetFileType (handle); + if (ft != FILE_TYPE_UNKNOWN || GetLastError () != ERROR_INVALID_HANDLE) + { + /* See if we can consoleify it */ + if (GetConsoleScreenBufferInfo (handle, &buf)) + { + if (ISSTATE (myself, PID_USETTY)) + dev.parse ("/dev/tty"); + else + dev = *console_dev; + } + else if (GetNumberOfConsoleInputEvents (handle, (DWORD *) &buf)) + { + if (ISSTATE (myself, PID_USETTY)) + dev.parse ("/dev/tty"); + else + dev = *console_dev; + } + else if (ft == FILE_TYPE_PIPE) + { + if (fd == 0) + dev = *piper_dev; + else + dev = *pipew_dev; + } + else if (wsock_started && getpeername ((SOCKET) handle, &sa, &sal) == 0) + dev = *tcp_dev; + else if (GetCommState (handle, &dcb)) + dev.parse ("/dev/ttyS0"); + else + { + name = handle_to_fn (handle, (char *) alloca (CYG_MAX_PATH + 100)); + bin = 0; + } + } + + if (!name && !dev) + fds[fd] = NULL; + else + { + fhandler_base *fh; + + if (dev) + fh = build_fh_dev (dev); + else + fh = build_fh_name (name); + + if (fh) + cygheap->fdtab[fd] = fh; + + if (!bin) + { + bin = fh->get_default_fmode (O_RDWR); + if (bin) + /* nothing */; + else if (dev) + bin = O_BINARY; + else if (name != unknown_file) + bin = fh->pc_binmode (); + } + + fh->init (handle, GENERIC_READ | GENERIC_WRITE, bin); + set_std_handle (fd); + paranoid_printf ("fd %d, handle %p", fd, handle); + } +} + +#define cnew(name) new ((void *) ccalloc (HEAP_FHANDLER, 1, sizeof (name))) name +fhandler_base * +build_fh_name (const char *name, HANDLE h, unsigned opt, suffix_info *si) +{ + path_conv pc (name, opt | PC_NULLEMPTY | PC_FULL | PC_POSIX, si); + if (pc.error) + { + fhandler_base *fh = cnew (fhandler_nodevice) (); + fh->set_error (pc.error); + set_errno (pc.error); + return fh; + } + + if (!pc.exists () && h) + pc.fillin (h); + + return build_fh_pc (pc); +} + +fhandler_base * +build_fh_dev (const device& dev, const char *unix_name) +{ + path_conv pc (dev); + if (unix_name) + pc.set_normalized_path (unix_name); + else + pc.set_normalized_path (dev.name); + return build_fh_pc (pc); +} + +fhandler_base * +build_fh_pc (path_conv& pc) +{ + fhandler_base *fh = NULL; + + switch (pc.dev.major) + { + case DEV_TTYS_MAJOR: + fh = cnew (fhandler_tty_slave) (); + break; + case DEV_TTYM_MAJOR: + fh = cnew (fhandler_tty_master) (); + break; + case DEV_CYGDRIVE_MAJOR: + fh = cnew (fhandler_cygdrive) (); + break; + case DEV_FLOPPY_MAJOR: + case DEV_CDROM_MAJOR: + case DEV_SD_MAJOR: + fh = cnew (fhandler_dev_floppy) (); + break; + case DEV_TAPE_MAJOR: + fh = cnew (fhandler_dev_tape) (); + break; + case DEV_SERIAL_MAJOR: + fh = cnew (fhandler_serial) (); + break; + default: + switch (pc.dev) + { + case FH_CONSOLE: + case FH_CONIN: + case FH_CONOUT: + fh = cnew (fhandler_console) (); + break; + case FH_PTYM: + fh = cnew (fhandler_pty_master) (); + break; + case FH_WINDOWS: + fh = cnew (fhandler_windows) (); + break; + case FH_FIFO: + fh = cnew (fhandler_fifo) (); + break; + case FH_PIPE: + case FH_PIPER: + case FH_PIPEW: + fh = cnew (fhandler_pipe) (); + break; + case FH_TCP: + case FH_UDP: + case FH_ICMP: + case FH_UNIX: + case FH_STREAM: + case FH_DGRAM: + fh = cnew (fhandler_socket) (); + break; + case FH_FS: + fh = cnew (fhandler_disk_file) (); + break; + case FH_NULL: + fh = cnew (fhandler_dev_null) (); + break; + case FH_ZERO: + fh = cnew (fhandler_dev_zero) (); + break; + case FH_RANDOM: + case FH_URANDOM: + fh = cnew (fhandler_dev_random) (); + break; + case FH_MEM: + case FH_PORT: + fh = cnew (fhandler_dev_mem) (); + break; + case FH_CLIPBOARD: + fh = cnew (fhandler_dev_clipboard) (); + break; + case FH_OSS_DSP: + fh = cnew (fhandler_dev_dsp) (); + break; + case FH_PROC: + fh = cnew (fhandler_proc) (); + break; + case FH_REGISTRY: + fh = cnew (fhandler_registry) (); + break; + case FH_PROCESS: + fh = cnew (fhandler_process) (); + break; + case FH_TTY: + { + if (myself->ctty == TTY_CONSOLE) + fh = cnew (fhandler_console) (); + else if (myself->ctty >= 0) + fh = cnew (fhandler_tty_slave) (); + break; + } + } + } + + if (!fh) + fh = cnew (fhandler_nodevice) (); + + fh->set_name (pc); + + debug_printf ("fh %p", fh); + return fh; +} + +fhandler_base * +dtable::dup_worker (fhandler_base *oldfh) +{ + fhandler_base *newfh = build_fh_pc (oldfh->pc); + *newfh = *oldfh; + newfh->set_io_handle (NULL); + if (oldfh->dup (newfh)) + { + cfree (newfh); + newfh = NULL; + return NULL; + } + + newfh->close_on_exec (false); + MALLOC_CHECK; + debug_printf ("duped '%s' old %p, new %p", oldfh->get_name (), oldfh->get_io_handle (), newfh->get_io_handle ()); + return newfh; +} + +int +dtable::dup2 (int oldfd, int newfd) +{ + int res = -1; + fhandler_base *newfh = NULL; // = NULL to avoid an incorrect warning + + MALLOC_CHECK; + debug_printf ("dup2 (%d, %d)", oldfd, newfd); + lock (); + + if (not_open (oldfd)) + { + syscall_printf ("fd %d not open", oldfd); + set_errno (EBADF); + goto done; + } + + if (newfd < 0) + { + syscall_printf ("new fd out of bounds: %d", newfd); + set_errno (EBADF); + goto done; + } + + if (newfd == oldfd) + { + res = 0; + goto done; + } + + if ((newfh = dup_worker (fds[oldfd])) == NULL) + { + res = -1; + goto done; + } + + debug_printf ("newfh->io_handle %p, oldfh->io_handle %p", + newfh->get_io_handle (), fds[oldfd]->get_io_handle ()); + + if (!not_open (newfd)) + close (newfd); + else if ((size_t) newfd < size) + /* nothing to do */; + else if (find_unused_handle (newfd) < 0) + { + newfh->close (); + res = -1; + goto done; + } + + fds[newfd] = newfh; + + if ((res = newfd) <= 2) + set_std_handle (res); + +done: + MALLOC_CHECK; + unlock (); + syscall_printf ("%d = dup2 (%d, %d)", res, oldfd, newfd); + + return res; +} + +fhandler_fifo * +dtable::find_fifo (const char *path) +{ + lock (); + fhandler_fifo *fh_res = NULL; + for (unsigned i = 0; i < size; i++) + { + fhandler_base *fh = fds[i]; + if (fh && fh->isfifo () && strcmp (path, fh->get_win32_name ()) == 0) + { + fh_res = (fhandler_fifo *) fh; + break; + } + } + unlock (); + return fh_res; +} + +select_record * +dtable::select_read (int fd, select_record *s) +{ + if (not_open (fd)) + { + set_errno (EBADF); + return NULL; + } + fhandler_base *fh = fds[fd]; + s = fh->select_read (s); + s->fd = fd; + s->fh = fh; + s->saw_error = 0; + debug_printf ("%s fd %d", fh->get_name (), fd); + return s; +} + +select_record * +dtable::select_write (int fd, select_record *s) +{ + if (not_open (fd)) + { + set_errno (EBADF); + return NULL; + } + fhandler_base *fh = fds[fd]; + s = fh->select_write (s); + s->fd = fd; + s->fh = fh; + s->saw_error = 0; + debug_printf ("%s fd %d", fh->get_name (), fd); + return s; +} + +select_record * +dtable::select_except (int fd, select_record *s) +{ + if (not_open (fd)) + { + set_errno (EBADF); + return NULL; + } + fhandler_base *fh = fds[fd]; + s = fh->select_except (s); + s->fd = fd; + s->fh = fh; + s->saw_error = 0; + debug_printf ("%s fd %d", fh->get_name (), fd); + return s; +} + +/* Function to walk the fd table after an exec and perform + per-fhandler type fixups. */ +void +dtable::fixup_before_fork (DWORD target_proc_id) +{ + lock (); + fhandler_base *fh; + for (size_t i = 0; i < size; i++) + if ((fh = fds[i]) != NULL) + { + debug_printf ("fd %d (%s)", i, fh->get_name ()); + fh->fixup_before_fork_exec (target_proc_id); + } + unlock (); +} + +void +dtable::fixup_before_exec (DWORD target_proc_id) +{ + lock (); + fhandler_base *fh; + for (size_t i = 0; i < size; i++) + if ((fh = fds[i]) != NULL && !fh->close_on_exec ()) + { + debug_printf ("fd %d (%s)", i, fh->get_name ()); + fh->fixup_before_fork_exec (target_proc_id); + } + unlock (); +} + +void +dtable::set_file_pointers_for_exec () +{ + lock (); + fhandler_base *fh; + for (size_t i = 0; i < size; i++) + if ((fh = fds[i]) != NULL && fh->get_flags () & O_APPEND) + SetFilePointer (fh->get_handle (), 0, 0, FILE_END); + unlock (); +} + +void +dtable::fixup_after_exec () +{ + first_fd_for_open = 0; + fhandler_base *fh; + cygheap->fdtab.init_lock (); + for (size_t i = 0; i < size; i++) + if ((fh = fds[i]) != NULL) + { + fh->clear_readahead (); + if (fh->close_on_exec ()) + { + if (fh->archetype) + fh->close (); + release (i); + } + else + { + fh->fixup_after_exec (); + if (i == 0) + SetStdHandle (std_consts[i], fh->get_io_handle ()); + else if (i <= 2) + SetStdHandle (std_consts[i], fh->get_output_handle ()); + } + } +} + +void +dtable::fixup_after_fork (HANDLE parent) +{ + fhandler_base *fh; + cygheap->fdtab.init_lock (); + for (size_t i = 0; i < size; i++) + if ((fh = fds[i]) != NULL) + { + if (fh->close_on_exec () || fh->need_fork_fixup ()) + { + debug_printf ("fd %d (%s)", i, fh->get_name ()); + fh->fixup_after_fork (parent); + } + if (i == 0) + SetStdHandle (std_consts[i], fh->get_io_handle ()); + else if (i <= 2) + SetStdHandle (std_consts[i], fh->get_output_handle ()); + } +} + +#ifdef NEWVFORK +int +dtable::vfork_child_dup () +{ + fhandler_base **newtable; + lock (); + newtable = (fhandler_base **) ccalloc (HEAP_ARGV, size, sizeof (fds[0])); + int res = 1; + + /* Remove impersonation */ + cygheap->user.deimpersonate (); + if (cygheap->ctty) + { + cygheap->ctty->usecount++; + cygheap->open_fhs++; + report_tty_counts (cygheap->ctty, "vfork dup", "incremented ", ""); + } + + for (size_t i = 0; i < size; i++) + if (not_open (i)) + continue; + else if ((newtable[i] = dup_worker (fds[i])) != NULL) + newtable[i]->set_close_on_exec (fds[i]->close_on_exec ()); + else + { + res = 0; + set_errno (EBADF); + goto out; + } + + fds_on_hold = fds; + fds = newtable; + +out: + /* Restore impersonation */ + cygheap->user.reimpersonate (); + + unlock (); + return 1; +} + +void +dtable::vfork_parent_restore () +{ + lock (); + + fhandler_tty_slave *ctty_on_hold = cygheap->ctty_on_hold; + close_all_files (); + fhandler_base **deleteme = fds; + fds = fds_on_hold; + fds_on_hold = NULL; + cfree (deleteme); + unlock (); + + if (cygheap->ctty != ctty_on_hold) + { + cygheap->ctty = ctty_on_hold; // revert + cygheap->ctty->close (); // Undo previous bump of this archetype + } + cygheap->ctty_on_hold = NULL; + + return; +} + +void +dtable::vfork_child_fixup () +{ + if (!fds_on_hold) + return; + debug_printf ("here"); + fhandler_base **saveme = fds; + fds = fds_on_hold; + + fhandler_base *fh; + for (int i = 0; i < (int) size; i++) + if ((fh = fds[i]) != NULL) + { + fh->clear_readahead (); + if (!fh->archetype && fh->close_on_exec ()) + release (i); + else + { + fh->close (); + release (i); + } + } + + fds = saveme; + cfree (fds_on_hold); + fds_on_hold = NULL; + + if (cygheap->ctty_on_hold) + { + cygheap->ctty_on_hold->close (); + cygheap->ctty_on_hold = NULL; + } + + return; +} +#endif /*NEWVFORK*/ + +#define DEVICE_PREFIX "\\device\\" +#define DEVICE_PREFIX_LEN sizeof (DEVICE_PREFIX) - 1 +#define REMOTE "\\Device\\LanmanRedirector\\" +#define REMOTE_LEN sizeof (REMOTE) - 1 + +static const char * +handle_to_fn (HANDLE h, char *posix_fn) +{ + OBJECT_NAME_INFORMATION *ntfn; + char fnbuf[32768]; + + memset (fnbuf, 0, sizeof (fnbuf)); + ntfn = (OBJECT_NAME_INFORMATION *) fnbuf; + ntfn->Name.MaximumLength = sizeof (fnbuf) - sizeof (*ntfn); + ntfn->Name.Buffer = (WCHAR *) (ntfn + 1); + + DWORD res = NtQueryObject (h, ObjectNameInformation, ntfn, sizeof (fnbuf), NULL); + + if (res) + { + strcpy (posix_fn, unknown_file); + debug_printf ("NtQueryObject failed"); + return unknown_file; + } + + // NT seems to do this on an unopened file + if (!ntfn->Name.Buffer) + { + debug_printf ("nt->Name.Buffer == NULL"); + return NULL; + } + + ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = 0; + + char win32_fn[CYG_MAX_PATH + 100]; + sys_wcstombs (win32_fn, ntfn->Name.Buffer, ntfn->Name.Length); + debug_printf ("nt name '%s'", win32_fn); + if (!strncasematch (win32_fn, DEVICE_PREFIX, DEVICE_PREFIX_LEN) + || !QueryDosDevice (NULL, fnbuf, sizeof (fnbuf))) + return strcpy (posix_fn, win32_fn); + + char *p = strechr (win32_fn + DEVICE_PREFIX_LEN, '\\'); + + int n = p - win32_fn; + int maxmatchlen = 0; + char *maxmatchdos = NULL; + for (char *s = fnbuf; *s; s = strchr (s, '\0') + 1) + { + char device[CYG_MAX_PATH + 10]; + device[CYG_MAX_PATH + 9] = '\0'; + if (strchr (s, ':') == NULL) + continue; + if (!QueryDosDevice (s, device, sizeof (device) - 1)) + continue; + char *q = strrchr (device, ';'); + if (q) + { + char *r = strchr (q, '\\'); + if (r) + strcpy (q, r + 1); + } + int devlen = strlen (device); + if (device[devlen - 1] == '\\') + device[--devlen] = '\0'; + if (devlen < maxmatchlen) + continue; + if (!strncasematch (device, win32_fn, devlen) || + (win32_fn[devlen] != '\0' && win32_fn[devlen] != '\\')) + continue; + maxmatchlen = devlen; + maxmatchdos = s; + debug_printf ("current match '%s'", device); + } + + char *w32 = win32_fn; + if (maxmatchlen) + { + n = strlen (maxmatchdos); + if (maxmatchdos[n - 1] == '\\') + n--; + w32 += maxmatchlen - n; + memcpy (w32, maxmatchdos, n); + w32[n] = '\\'; + } + else if (strncasematch (w32, REMOTE, REMOTE_LEN)) + { + w32 += REMOTE_LEN - 2; + *w32 = '\\'; + debug_printf ("remote drive"); + } + + + debug_printf ("derived path '%s'", w32); + cygwin_conv_to_full_posix_path (w32, posix_fn); + return posix_fn; +} diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc new file mode 100644 index 00000000000..2ac13588aec --- /dev/null +++ b/winsup/cygwin/exceptions.cc @@ -0,0 +1,1206 @@ +/* exceptions.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include + +#include "exceptions.h" +#include "sync.h" +#include "pinfo.h" +#include "cygtls.h" +#include "sigproc.h" +#include "cygerrno.h" +#define NEED_VFORK +#include "perthread.h" +#include "shared_info.h" +#include "perprocess.h" +#include "security.h" +#include "cygthread.h" + +#define CALL_HANDLER_RETRY 20 + +char debugger_command[2 * CYG_MAX_PATH + 20]; + +extern "C" { +static int handle_exceptions (EXCEPTION_RECORD *, void *, CONTEXT *, void *); +extern void sigdelayed (); +}; + +extern DWORD sigtid; + +extern HANDLE hExeced; +extern DWORD dwExeced; + +static BOOL WINAPI ctrl_c_handler (DWORD); +static void signal_exit (int) __attribute__ ((noreturn)); +static char windows_system_directory[1024]; +static size_t windows_system_directory_length; + +/* This is set to indicate that we have already exited. */ + +static NO_COPY int exit_already = 0; +static NO_COPY muto *mask_sync = NULL; + +NO_COPY static struct +{ + unsigned int code; + const char *name; +} status_info[] = +{ +#define X(s) s, #s + { X (STATUS_ABANDONED_WAIT_0) }, + { X (STATUS_ACCESS_VIOLATION) }, + { X (STATUS_ARRAY_BOUNDS_EXCEEDED) }, + { X (STATUS_BREAKPOINT) }, + { X (STATUS_CONTROL_C_EXIT) }, + { X (STATUS_DATATYPE_MISALIGNMENT) }, + { X (STATUS_FLOAT_DENORMAL_OPERAND) }, + { X (STATUS_FLOAT_DIVIDE_BY_ZERO) }, + { X (STATUS_FLOAT_INEXACT_RESULT) }, + { X (STATUS_FLOAT_INVALID_OPERATION) }, + { X (STATUS_FLOAT_OVERFLOW) }, + { X (STATUS_FLOAT_STACK_CHECK) }, + { X (STATUS_FLOAT_UNDERFLOW) }, + { X (STATUS_GUARD_PAGE_VIOLATION) }, + { X (STATUS_ILLEGAL_INSTRUCTION) }, + { X (STATUS_INTEGER_DIVIDE_BY_ZERO) }, + { X (STATUS_INTEGER_OVERFLOW) }, + { X (STATUS_INVALID_DISPOSITION) }, + { X (STATUS_IN_PAGE_ERROR) }, + { X (STATUS_NONCONTINUABLE_EXCEPTION) }, + { X (STATUS_NO_MEMORY) }, + { X (STATUS_PENDING) }, + { X (STATUS_PRIVILEGED_INSTRUCTION) }, + { X (STATUS_SINGLE_STEP) }, + { X (STATUS_STACK_OVERFLOW) }, + { X (STATUS_TIMEOUT) }, + { X (STATUS_USER_APC) }, + { X (STATUS_WAIT_0) }, + { 0, 0 } +#undef X +}; + +/* Initialization code. */ + +// Set up the exception handler for the current thread. The PowerPC & Mips +// use compiler generated tables to set up the exception handlers for each +// region of code, and the kernel walks the call list until it finds a region +// of code that handles exceptions. The x86 on the other hand uses segment +// register fs, offset 0 to point to the current exception handler. + +extern exception_list *_except_list asm ("%fs:0"); + +void +init_exception_handler (exception_list *el, exception_handler *eh) +{ + el->handler = eh; + el->prev = _except_list; + _except_list = el; +} + +extern "C" void +init_exceptions (exception_list *el) +{ + init_exception_handler (el, handle_exceptions); +} + +void +init_console_handler () +{ + (void) SetConsoleCtrlHandler (ctrl_c_handler, FALSE); + if (!SetConsoleCtrlHandler (ctrl_c_handler, TRUE)) + system_printf ("SetConsoleCtrlHandler failed, %E"); +} + +extern "C" void +error_start_init (const char *buf) +{ + if (!buf || !*buf) + { + debugger_command[0] = '\0'; + return; + } + + char pgm[CYG_MAX_PATH + 1]; + if (!GetModuleFileName (NULL, pgm, CYG_MAX_PATH)) + strcpy (pgm, "cygwin1.dll"); + for (char *p = strchr (pgm, '\\'); p; p = strchr (p, '\\')) + *p = '/'; + + __small_sprintf (debugger_command, "%s \"%s\"", buf, pgm); +} + +static void +open_stackdumpfile () +{ + if (myself->progname[0]) + { + const char *p; + /* write to progname.stackdump if possible */ + if (!myself->progname[0]) + p = "unknown"; + else if ((p = strrchr (myself->progname, '\\'))) + p++; + else + p = myself->progname; + char corefile[strlen (p) + sizeof (".stackdump")]; + __small_sprintf (corefile, "%s.stackdump", p); + HANDLE h = CreateFile (corefile, GENERIC_WRITE, 0, &sec_none_nih, + CREATE_ALWAYS, 0, 0); + if (h != INVALID_HANDLE_VALUE) + { + if (!myself->cygstarted) + system_printf ("Dumping stack trace to %s", corefile); + else + debug_printf ("Dumping stack trace to %s", corefile); + SetStdHandle (STD_ERROR_HANDLE, h); + } + } +} + +/* Utilities for dumping the stack, etc. */ + +static void +exception (EXCEPTION_RECORD *e, CONTEXT *in) +{ + const char *exception_name = NULL; + + if (e) + { + for (int i = 0; status_info[i].name; i++) + { + if (status_info[i].code == e->ExceptionCode) + { + exception_name = status_info[i].name; + break; + } + } + } + + if (exception_name) + small_printf ("Exception: %s at eip=%08x\r\n", exception_name, in->Eip); + else + small_printf ("Exception %d at eip=%08x\r\n", e->ExceptionCode, in->Eip); + small_printf ("eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\r\n", + in->Eax, in->Ebx, in->Ecx, in->Edx, in->Esi, in->Edi); + small_printf ("ebp=%08x esp=%08x program=%s, pid %u, thread %s\r\n", + in->Ebp, in->Esp, myself->progname, myself->pid, cygthread::name ()); + small_printf ("cs=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\r\n", + in->SegCs, in->SegDs, in->SegEs, in->SegFs, in->SegGs, in->SegSs); +} + +/* A class for manipulating the stack. */ +class stack_info +{ + int walk (); /* Uses the "old" method */ + char *next_offset () {return *((char **) sf.AddrFrame.Offset);} + bool needargs; + DWORD dummy_frame; +public: + STACKFRAME sf; /* For storing the stack information */ + void init (DWORD, bool, bool); /* Called the first time that stack info is needed */ + + /* Postfix ++ iterates over the stack, returning zero when nothing is left. */ + int operator ++(int) { return walk (); } +}; + +/* The number of parameters used in STACKFRAME */ +#define NPARAMS (sizeof (thestack.sf.Params) / sizeof (thestack.sf.Params[0])) + +/* This is the main stack frame info for this process. */ +static NO_COPY stack_info thestack; + +/* Initialize everything needed to start iterating. */ +void +stack_info::init (DWORD ebp, bool wantargs, bool goodframe) +{ +# define debp ((DWORD *) ebp) + memset (&sf, 0, sizeof (sf)); + if (!goodframe) + sf.AddrFrame.Offset = ebp; + else + { + dummy_frame = ebp; + sf.AddrFrame.Offset = (DWORD) &dummy_frame; + } + sf.AddrReturn.Offset = debp[1]; + sf.AddrFrame.Mode = AddrModeFlat; + needargs = wantargs; +# undef debp +} + +/* Walk the stack by looking at successive stored 'bp' frames. + This is not foolproof. */ +int +stack_info::walk () +{ + char **ebp; + if ((ebp = (char **) next_offset ()) == NULL) + return 0; + + sf.AddrFrame.Offset = (DWORD) ebp; + sf.AddrPC.Offset = sf.AddrReturn.Offset; + + if (!sf.AddrPC.Offset) + return 0; /* stack frames are exhausted */ + + /* The return address always follows the stack pointer */ + sf.AddrReturn.Offset = (DWORD) *++ebp; + + if (needargs) + /* The arguments follow the return address */ + for (unsigned i = 0; i < NPARAMS; i++) + sf.Params[i] = (DWORD) *++ebp; + + return 1; +} + +static void +stackdump (DWORD ebp, int open_file, bool isexception) +{ + extern unsigned long rlim_core; + + if (rlim_core == 0UL) + return; + + if (open_file) + open_stackdumpfile (); + + int i; + + thestack.init (ebp, 1, !isexception); /* Initialize from the input CONTEXT */ + small_printf ("Stack trace:\r\nFrame Function Args\r\n"); + for (i = 0; i < 16 && thestack++; i++) + { + small_printf ("%08x %08x ", thestack.sf.AddrFrame.Offset, + thestack.sf.AddrPC.Offset); + for (unsigned j = 0; j < NPARAMS; j++) + small_printf ("%s%08x", j == 0 ? " (" : ", ", thestack.sf.Params[j]); + small_printf (")\r\n"); + } + small_printf ("End of stack trace%s\n", + i == 16 ? " (more stack frames may be present)" : ""); +} + +/* Temporary (?) function for external callers to get a stack dump */ +extern "C" void +cygwin_stackdump () +{ + CONTEXT c; + c.ContextFlags = CONTEXT_FULL; + GetThreadContext (GetCurrentThread (), &c); + stackdump (c.Ebp, 0, 0); +} + +#define TIME_TO_WAIT_FOR_DEBUGGER 10000 + +extern "C" int +try_to_debug (bool waitloop) +{ + debug_printf ("debugger_command '%s'", debugger_command); + if (*debugger_command == '\0' || being_debugged ()) + return 0; + + __small_sprintf (strchr (debugger_command, '\0'), " %u", GetCurrentProcessId ()); + + LONG prio = GetThreadPriority (GetCurrentThread ()); + SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST); + PROCESS_INFORMATION pi = {NULL, 0, 0, 0}; + + STARTUPINFO si = {0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}; + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.dwFlags = 0; + si.cb = sizeof (si); + + /* FIXME: need to know handles of all running threads to + suspend_all_threads_except (current_thread_id); + */ + + /* if any of these mutexes is owned, we will fail to start any cygwin app + until trapped app exits */ + + ReleaseMutex (tty_mutex); + + /* prevent recursive exception handling */ + char* rawenv = GetEnvironmentStrings () ; + for (char* p = rawenv; *p != '\0'; p = strchr (p, '\0') + 1) + { + if (strncmp (p, "CYGWIN=", strlen ("CYGWIN=")) == 0) + { + char* q = strstr (p, "error_start") ; + /* replace 'error_start=...' with '_rror_start=...' */ + if (q) + { + *q = '_' ; + SetEnvironmentVariable ("CYGWIN", p + strlen ("CYGWIN=")) ; + } + break ; + } + } + + console_printf ("*** starting debugger for pid %u, tid %u\n", + cygwin_pid (GetCurrentProcessId ()), GetCurrentThreadId ()); + BOOL dbg; + dbg = CreateProcess (NULL, + debugger_command, + NULL, + NULL, + FALSE, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi); + + if (!dbg) + system_printf ("Failed to start debugger, %E"); + else + { + if (!waitloop) + return dbg; + SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_IDLE); + while (!being_debugged ()) + Sleep (0); + Sleep (2000); + } + + console_printf ("*** continuing pid %u from debugger call (%d)\n", + cygwin_pid (GetCurrentProcessId ()), dbg); + + SetThreadPriority (GetCurrentThread (), prio); + return dbg; +} + +/* Main exception handler. */ + +extern "C" DWORD __stdcall RtlUnwind (void *, void *, void *, DWORD); +static int +handle_exceptions (EXCEPTION_RECORD *e0, void *frame, CONTEXT *in0, void *) +{ + static bool NO_COPY debugging = false; + static int NO_COPY recursed = 0; + + if (debugging && ++debugging < 500000) + { + SetThreadPriority (hMainThread, THREAD_PRIORITY_NORMAL); + return 0; + } + + /* If we've already exited, don't do anything here. Returning 1 + tells Windows to keep looking for an exception handler. */ + if (exit_already) + return 1; + + EXCEPTION_RECORD e = *e0; + CONTEXT in = *in0; + + siginfo_t si; + /* Coerce win32 value to posix value. */ + switch (e.ExceptionCode) + { + case STATUS_FLOAT_DENORMAL_OPERAND: + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_FLOAT_INVALID_OPERATION: + case STATUS_FLOAT_STACK_CHECK: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_FLTSUB; + break; + case STATUS_FLOAT_INEXACT_RESULT: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_FLTRES; + break; + case STATUS_FLOAT_OVERFLOW: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_FLTOVF; + break; + case STATUS_FLOAT_UNDERFLOW: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_FLTUND; + break; + case STATUS_INTEGER_DIVIDE_BY_ZERO: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_INTDIV; + break; + case STATUS_INTEGER_OVERFLOW: + si.si_signo = SIGFPE; + si.si_sigval.sival_int = FPE_INTOVF; + break; + + case STATUS_ILLEGAL_INSTRUCTION: + si.si_signo = SIGILL; + si.si_sigval.sival_int = ILL_ILLOPC; + break; + + case STATUS_PRIVILEGED_INSTRUCTION: + si.si_signo = SIGILL; + si.si_sigval.sival_int = ILL_PRVOPC; + break; + + case STATUS_NONCONTINUABLE_EXCEPTION: + si.si_signo = SIGILL; + si.si_sigval.sival_int = ILL_ILLADR; + break; + + case STATUS_TIMEOUT: + si.si_signo = SIGALRM; + si.si_sigval.sival_int = 0; + break; + + case STATUS_ACCESS_VIOLATION: + case STATUS_DATATYPE_MISALIGNMENT: + case STATUS_ARRAY_BOUNDS_EXCEEDED: + case STATUS_GUARD_PAGE_VIOLATION: + case STATUS_IN_PAGE_ERROR: + case STATUS_NO_MEMORY: + case STATUS_INVALID_DISPOSITION: + case STATUS_STACK_OVERFLOW: + si.si_signo = SIGSEGV; + si.si_sigval.sival_int = SEGV_MAPERR; + break; + + case STATUS_CONTROL_C_EXIT: + si.si_signo = SIGINT; + si.si_sigval.sival_int = 0; + break; + + case STATUS_INVALID_HANDLE: + /* CloseHandle will throw this exception if it is given an + invalid handle. We don't care about the exception; we just + want CloseHandle to return an error. This can be revisited + if gcc ever supports Windows style structured exception + handling. */ + return 0; + + default: + /* If we don't recognize the exception, we have to assume that + we are doing structured exception handling, and we let + something else handle it. */ + return 1; + } + + debug_printf ("In cygwin_except_handler exc %p at %p sp %p", e.ExceptionCode, in.Eip, in.Esp); + debug_printf ("In cygwin_except_handler sig %d at %p", si.si_signo, in.Eip); + + if (global_sigs[si.si_signo].sa_mask & SIGTOMASK (si.si_signo)) + syscall_printf ("signal %d, masked %p", si.si_signo, + global_sigs[si.si_signo].sa_mask); + + debug_printf ("In cygwin_except_handler calling %p", + global_sigs[si.si_signo].sa_handler); + + DWORD *ebp = (DWORD *)in.Esp; + for (DWORD *bpend = (DWORD *) __builtin_frame_address (0); ebp > bpend; ebp--) + if (*ebp == in.SegCs && ebp[-1] == in.Eip) + { + ebp -= 2; + break; + } + + if (!cygwin_finished_initializing + || GetCurrentThreadId () == sigtid + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_DFL + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_IGN + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_ERR) + { + /* Print the exception to the console */ + if (!myself->cygstarted) + for (int i = 0; status_info[i].name; i++) + if (status_info[i].code == e.ExceptionCode) + { + system_printf ("Exception: %s", status_info[i].name); + break; + } + + /* Another exception could happen while tracing or while exiting. + Only do this once. */ + if (recursed++) + system_printf ("Error while dumping state (probably corrupted stack)"); + else + { + if (try_to_debug (0)) + { + debugging = true; + return 0; + } + + open_stackdumpfile (); + exception (&e, &in); + stackdump ((DWORD) ebp, 0, 1); + } + + signal_exit (0x80 | si.si_signo); // Flag signal + core dump + } + + extern DWORD ret_here[]; + RtlUnwind (frame, ret_here, e0, 0); + __asm__ volatile (".equ _ret_here,."); + + si.si_addr = ebp; + si.si_code = SI_KERNEL; + si.si_errno = si.si_pid = si.si_uid = 0; + _my_tls.push ((__stack_t) ebp, true); + sig_send (NULL, si, &_my_tls); // Signal myself + return 1; +} + +/* Utilities to call a user supplied exception handler. */ + +#define SIG_NONMASKABLE (SIGTOMASK (SIGKILL) | SIGTOMASK (SIGSTOP)) + +/* Non-raceable sigsuspend + * Note: This implementation is based on the Single UNIX Specification + * man page. This indicates that sigsuspend always returns -1 and that + * attempts to block unblockable signals will be silently ignored. + * This is counter to what appears to be documented in some UNIX + * man pages, e.g. Linux. + */ +int __stdcall +handle_sigsuspend (sigset_t tempmask) +{ + sigset_t oldmask = myself->getsigmask (); // Remember for restoration + + set_signal_mask (tempmask, oldmask); + sigproc_printf ("oldmask %p, newmask %p", oldmask, tempmask); + + pthread_testcancel (); + pthread::cancelable_wait (signal_arrived, INFINITE); + + set_sig_errno (EINTR); // Per POSIX + + /* A signal dispatch function will have been added to our stack and will + be hit eventually. Set the old mask to be restored when the signal + handler returns and indicate its presence by modifying deltamask. */ + + _my_tls.deltamask |= SIG_NONMASKABLE; + _my_tls.oldmask = oldmask; // Will be restored by signal handler + return -1; +} + +extern DWORD exec_exit; // Possible exit value for exec + +extern "C" { +static void +sig_handle_tty_stop (int sig) +{ + _my_tls.incyg = 1; + /* Silently ignore attempts to suspend if there is no accommodating + cygwin parent to deal with this behavior. */ + if (!myself->cygstarted) + { + myself->process_state &= ~PID_STOPPED; + return; + } + + myself->stopsig = sig; + char pipesig; + DWORD nb; + /* See if we have a living parent. If so, send it a special signal. + It will figure out exactly which pid has stopped by scanning + its list of subprocesses. */ + if (my_parent_is_alive ()) + { + pinfo parent (myself->ppid); + if (NOTSTATE (parent, PID_NOCLDSTOP)) + { + pipesig = sig; + if (!WriteFile (myself->wr_proc_pipe, &pipesig, 1, &nb, NULL)) + debug_printf ("sending stop notification to parent failed, %E"); + } + } + sigproc_printf ("process %d stopped by signal %d", myself->pid, sig); + HANDLE w4[2]; + w4[0] = sigCONT; + w4[1] = signal_arrived; + switch (WaitForMultipleObjects (2, w4, TRUE, INFINITE)) + { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + reset_signal_arrived (); + if (my_parent_is_alive ()) + { + pinfo parent (myself->ppid); + if (parent) + { + sig = SIGCONT; + if (!WriteFile (myself->wr_proc_pipe, &sig, 1, &nb, NULL)) + debug_printf ("sending stop notification to parent failed, %E"); + } + } + break; + default: + api_fatal ("WaitSingleObject failed, %E"); + break; + } + _my_tls.incyg = 0; + return; +} +} + +bool +interruptible (DWORD pc) +{ + int res; + MEMORY_BASIC_INFORMATION m; + + memset (&m, 0, sizeof m); + if (!VirtualQuery ((LPCVOID) pc, &m, sizeof m)) + sigproc_printf ("couldn't get memory info, pc %p, %E", pc); + + char *checkdir = (char *) alloca (windows_system_directory_length + 4); + memset (checkdir, 0, sizeof (checkdir)); + +# define h ((HMODULE) m.AllocationBase) + /* Apparently Windows 95 can sometimes return bogus addresses from + GetThreadContext. These resolve to a strange allocation base. + These should *never* be treated as interruptible. */ + if (!h || m.State != MEM_COMMIT) + res = false; + else if (h == user_data->hmodule) + res = true; + else if (!GetModuleFileName (h, checkdir, windows_system_directory_length + 2)) + res = false; + else + res = !strncasematch (windows_system_directory, checkdir, + windows_system_directory_length); + sigproc_printf ("pc %p, h %p, interruptible %d", pc, h, res); +# undef h + return res; +} +void __stdcall +_cygtls::interrupt_setup (int sig, void *handler, struct sigaction& siga) +{ + push ((__stack_t) sigdelayed, false); + deltamask = (siga.sa_mask | SIGTOMASK (sig)) & ~SIG_NONMASKABLE; + sa_flags = siga.sa_flags; + func = (void (*) (int)) handler; + saved_errno = -1; // Flag: no errno to save + if (handler == sig_handle_tty_stop) + { + myself->stopsig = 0; + myself->process_state |= PID_STOPPED; + } + + this->sig = sig; // Should always be last thing set to avoid a race + + /* Clear any waiting threads prior to dispatching to handler function */ + int res = SetEvent (signal_arrived); // For an EINTR case + proc_subproc (PROC_CLEARWAIT, 1); + sigproc_printf ("armed signal_arrived %p, sig %d, res %d", signal_arrived, + sig, res); +} + +bool +_cygtls::interrupt_now (CONTEXT *ctx, int sig, void *handler, + struct sigaction& siga) +{ + push ((__stack_t) ctx->Eip, false); + interrupt_setup (sig, handler, siga); + ctx->Eip = pop (); + SetThreadContext (*this, ctx); /* Restart the thread in a new location */ + return 1; +} + +extern "C" void __stdcall +set_sig_errno (int e) +{ + *_my_tls.errno_addr = e; + _my_tls.saved_errno = e; + // sigproc_printf ("errno %d", e); +} + +static int setup_handler (int, void *, struct sigaction&, _cygtls *tls) + __attribute__((regparm(3))); +static int +setup_handler (int sig, void *handler, struct sigaction& siga, _cygtls *tls) +{ + CONTEXT cx; + bool interrupted = false; + + if (tls->sig) + { + sigproc_printf ("trying to send sig %d but signal %d already armed", + sig, tls->sig); + goto out; + } + + for (int i = 0; i < CALL_HANDLER_RETRY; i++) + { + tls->lock (); + if (tls->incyg || tls->in_exception ()) + { + sigproc_printf ("controlled interrupt. incyg %d, exception %d, stackptr %p, stack %p, stackptr[-1] %p", + tls->incyg, tls->in_exception (), tls->stackptr, tls->stack, tls->stackptr[-1]); + tls->reset_exception (); + tls->interrupt_setup (sig, handler, siga); + interrupted = true; + tls->unlock (); + break; + } + + tls->unlock (); + DWORD res; + HANDLE hth = (HANDLE) *tls; + + /* Suspend the thread which will receive the signal. + For Windows 95, we also have to ensure that the addresses returned by + GetThreadContext are valid. + If one of these conditions is not true we loop for a fixed number of times + since we don't want to stall the signal handler. FIXME: Will this result in + noticeable delays? + If the thread is already suspended (which can occur when a program has called + SuspendThread on itself) then just queue the signal. */ + +#ifndef DEBUGGING + sigproc_printf ("suspending mainthread"); +#else + cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext (hth, &cx)) + memset (&cx, 0, sizeof cx); + sigproc_printf ("suspending mainthread PC %p", cx.Eip); +#endif + res = SuspendThread (hth); + /* Just set pending if thread is already suspended */ + if (res) + { + (void) ResumeThread (hth); + break; + } + if (tls->incyg || tls->in_exception () || tls->spinning || tls->locked ()) + sigproc_printf ("incyg %d, in_exception %d, spinning %d, locked %d\n", + tls->incyg, tls->in_exception (), tls->spinning, tls->locked ()); + else + { + cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext (hth, &cx)) + system_printf ("couldn't get context of main thread, %E"); + else if (interruptible (cx.Eip)) + interrupted = tls->interrupt_now (&cx, sig, handler, siga); + } + + res = ResumeThread (hth); + if (interrupted) + break; + + sigproc_printf ("couldn't interrupt. trying again."); + low_priority_sleep (0); + } + +out: + if (interrupted && tls->event) + { + HANDLE h = tls->event; + tls->event = NULL; + SetEvent (h); + } + sigproc_printf ("signal %d %sdelivered", sig, interrupted ? "" : "not "); + return interrupted; +} + +/* Keyboard interrupt handler. */ +static BOOL WINAPI +ctrl_c_handler (DWORD type) +{ + static bool saw_close; + + if (!cygwin_finished_initializing) + { + if (myself->cygstarted) /* Was this process created by a cygwin process? */ + return TRUE; /* Yes. Let the parent eventually handle CTRL-C issues. */ + debug_printf ("exiting with status %p", STATUS_CONTROL_C_EXIT); + ExitProcess (STATUS_CONTROL_C_EXIT); + } + + _my_tls.remove (INFINITE); + + /* Return FALSE to prevent an "End task" dialog box from appearing + for each Cygwin process window that's open when the computer + is shut down or console window is closed. */ + + if (type == CTRL_SHUTDOWN_EVENT) + { +#if 0 + /* Don't send a signal. Only NT service applications and their child + processes will receive this event and the services typically already + handle the shutdown action when getting the SERVICE_CONTROL_SHUTDOWN + control message. */ + sig_send (NULL, SIGTERM); +#endif + return FALSE; + } + + if (myself->ctty != -1) + { + if (type == CTRL_CLOSE_EVENT) + { + sig_send (NULL, SIGHUP); + saw_close = true; + return FALSE; + } + if (!saw_close && type == CTRL_LOGOFF_EVENT) + { + /* Check if the process is actually associated with a visible + window station, one which actually represents a visible desktop. + If not, the CTRL_LOGOFF_EVENT doesn't concern this process. */ + if (has_visible_window_station ()) + sig_send (myself_nowait, SIGHUP); + return FALSE; + } + } + + /* If we are a stub and the new process has a pinfo structure, let it + handle this signal. */ + if (dwExeced && pinfo (dwExeced)) + return TRUE; + + /* We're only the process group leader when we have a valid pinfo structure. + If we don't have one, then the parent "stub" will handle the signal. */ + if (!pinfo (cygwin_pid (GetCurrentProcessId ()))) + return TRUE; + + tty_min *t = cygwin_shared->tty.get_tty (myself->ctty); + /* Ignore this if we're not the process group leader since it should be handled + *by* the process group leader. */ + if (myself->ctty != -1 && t->getpgid () == myself->pid && + (GetTickCount () - t->last_ctrl_c) >= MIN_CTRL_C_SLOP) + /* Otherwise we just send a SIGINT to the process group and return TRUE (to indicate + that we have handled the signal). At this point, type should be + a CTRL_C_EVENT or CTRL_BREAK_EVENT. */ + { + t->last_ctrl_c = GetTickCount (); + killsys (-myself->pid, SIGINT); + t->last_ctrl_c = GetTickCount (); + return TRUE; + } + + return TRUE; +} + +/* Function used by low level sig wrappers. */ +extern "C" void __stdcall +set_process_mask (sigset_t newmask) +{ + set_signal_mask (newmask); +} + +extern "C" int +sighold (int sig) +{ + /* check that sig is in right range */ + if (sig < 0 || sig >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("signal %d out of range", sig); + return -1; + } + mask_sync->acquire (INFINITE); + sigset_t mask = myself->getsigmask (); + sigaddset (&mask, sig); + set_signal_mask (mask); + mask_sync->release (); + return 0; +} + +/* Update the signal mask for this process + and return the old mask. + Called from sigdelayed */ +extern "C" sigset_t +set_process_mask_delta () +{ + mask_sync->acquire (INFINITE); + sigset_t newmask, oldmask; + + if (_my_tls.deltamask & SIG_NONMASKABLE) + oldmask = _my_tls.oldmask; /* from handle_sigsuspend */ + else + oldmask = myself->getsigmask (); + newmask = (oldmask | _my_tls.deltamask) & ~SIG_NONMASKABLE; + sigproc_printf ("oldmask %p, newmask %p, deltamask %p", oldmask, newmask, + _my_tls.deltamask); + myself->setsigmask (newmask); + mask_sync->release (); + return oldmask; +} + +/* Set the signal mask for this process. + Note that some signals are unmaskable, as in UNIX. */ +extern "C" void __stdcall +set_signal_mask (sigset_t newmask, sigset_t oldmask) +{ + mask_sync->acquire (INFINITE); + newmask &= ~SIG_NONMASKABLE; + sigset_t mask_bits = oldmask & ~newmask; + sigproc_printf ("oldmask %p, newmask %p, mask_bits %p", oldmask, newmask, + mask_bits); + myself->setsigmask (newmask); // Set a new mask + if (mask_bits) + sig_dispatch_pending (true); + else + sigproc_printf ("not calling sig_dispatch_pending"); + mask_sync->release (); + return; +} + +int __stdcall +sigpacket::process () +{ + DWORD continue_now; + if (si.si_signo != SIGCONT) + continue_now = false; + else + { + continue_now = myself->process_state & PID_STOPPED; + myself->stopsig = 0; + myself->process_state &= ~PID_STOPPED; + /* Clear pending stop signals */ + sig_clear (SIGSTOP); + sig_clear (SIGTSTP); + sig_clear (SIGTTIN); + sig_clear (SIGTTOU); + } + + int rc = 1; + + sigproc_printf ("signal %d processing", si.si_signo); + struct sigaction thissig = global_sigs[si.si_signo]; + + myself->rusage_self.ru_nsignals++; + + if (si.si_signo == SIGKILL) + goto exit_sig; + if ( si.si_signo == SIGSTOP) + { + sig_clear (SIGCONT); + if (!tls) + tls = _main_tls; + goto stop; + } + + bool masked; + bool special_case; + bool insigwait_mask; + insigwait_mask = masked = false; + if (special_case = (VFORKPID || ISSTATE (myself, PID_STOPPED))) + /* nothing to do */; + else if (tls && sigismember (&tls->sigwait_mask, si.si_signo)) + insigwait_mask = true; + else if (!tls && (tls = _cygtls::find_tls (si.si_signo))) + insigwait_mask = true; + else if (!(masked = sigismember (mask, si.si_signo)) && tls) + masked = sigismember (&tls->sigmask, si.si_signo); + + if (insigwait_mask) + goto thread_specific; + + if (!tls) + tls = _main_tls; + + if (special_case || masked) + { + sigproc_printf ("signal %d blocked", si.si_signo); + rc = -1; + goto done; + } + + void *handler; + handler = (void *) thissig.sa_handler; + + /* Clear pending SIGCONT on stop signals */ + if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU) + sig_clear (SIGCONT); + +#if 0 + char sigmsg[24]; + __small_sprintf (sigmsg, "cygwin: signal %d\n", si.si_signo); + OutputDebugString (sigmsg); +#endif + + if (handler == (void *) SIG_DFL) + { + if (insigwait_mask) + goto thread_specific; + if (si.si_signo == SIGCHLD || si.si_signo == SIGIO || si.si_signo == SIGCONT || si.si_signo == SIGWINCH + || si.si_signo == SIGURG) + { + sigproc_printf ("default signal %d ignored", si.si_signo); + if (continue_now) + SetEvent (signal_arrived); + goto done; + } + + if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU) + goto stop; + + goto exit_sig; + } + + if (handler == (void *) SIG_IGN) + { + sigproc_printf ("signal %d ignored", si.si_signo); + goto done; + } + + if (handler == (void *) SIG_ERR) + goto exit_sig; + + tls->set_siginfo (this); + goto dosig; + +stop: + /* Eat multiple attempts to STOP */ + if (ISSTATE (myself, PID_STOPPED)) + goto done; + handler = (void *) sig_handle_tty_stop; + thissig = global_sigs[SIGSTOP]; + +dosig: + /* Dispatch to the appropriate function. */ + sigproc_printf ("signal %d, about to call %p", si.si_signo, handler); + rc = setup_handler (si.si_signo, handler, thissig, tls); + +done: + if (continue_now) + SetEvent (sigCONT); + sigproc_printf ("returning %d", rc); + return rc; + +thread_specific: + tls->sig = si.si_signo; + tls->set_siginfo (this); + sigproc_printf ("releasing sigwait for thread"); + SetEvent (tls->event); + goto done; + +exit_sig: + if (si.si_signo == SIGQUIT || si.si_signo == SIGABRT) + { + CONTEXT c; + c.ContextFlags = CONTEXT_FULL; + GetThreadContext (hMainThread, &c); + if (!try_to_debug ()) + stackdump (c.Ebp, 1, 1); + si.si_signo |= 0x80; + } + sigproc_printf ("signal %d, about to call do_exit", si.si_signo); + signal_exit (si.si_signo); + /* Never returns */ +} + +CRITICAL_SECTION NO_COPY exit_lock; + +/* Cover function to `do_exit' to handle exiting even in presence of more + exceptions. We used to call exit, but a SIGSEGV shouldn't cause atexit + routines to run. */ +static void +signal_exit (int rc) +{ + EnterCriticalSection (&exit_lock); + if (exit_already++) + myself->exit (rc); + + /* We'd like to stop the main thread from executing but when we do that it + causes random, inexplicable hangs. So, instead, we set up the priority + of this thread really high so that it should do its thing and then exit. */ + (void) SetThreadPriority (hMainThread, THREAD_PRIORITY_IDLE); + (void) SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL); + + user_data->resourcelocks->Delete (); + user_data->resourcelocks->Init (); + + if (hExeced) + { + sigproc_printf ("terminating captive process"); + TerminateProcess (hExeced, rc); + } + + sigproc_printf ("about to call do_exit (%x)", rc); + (void) SetEvent (signal_arrived); + do_exit (rc); +} + +HANDLE NO_COPY tty_mutex = NULL; + +void +events_init (void) +{ + char *name; + char mutex_name[CYG_MAX_PATH]; + /* tty_mutex is on while searching for a tty slot. It's necessary + while finding console window handle */ + + if (!(tty_mutex = CreateMutex (&sec_all_nih, FALSE, + name = shared_name (mutex_name, + "tty_mutex", 0)))) + api_fatal ("can't create title mutex '%s', %E", name); + + ProtectHandle (tty_mutex); + new_muto (mask_sync); + windows_system_directory[0] = '\0'; + (void) GetSystemDirectory (windows_system_directory, sizeof (windows_system_directory) - 2); + char *end = strchr (windows_system_directory, '\0'); + if (end == windows_system_directory) + api_fatal ("can't find windows system directory"); + if (end[-1] != '\\') + { + *end++ = '\\'; + *end = '\0'; + } + windows_system_directory_length = end - windows_system_directory; + debug_printf ("windows_system_directory '%s', windows_system_directory_length %d", + windows_system_directory, windows_system_directory_length); + InitializeCriticalSection (&exit_lock); +} + +void +events_terminate (void) +{ + exit_already = 1; +} + +int +_cygtls::call_signal_handler () +{ + int this_sa_flags = 0; + /* Call signal handler. */ + while (sig) + { + lock (); unlock (); // make sure synchronized + this_sa_flags = sa_flags; + int thissig = sig; + void (*sigfunc) (int) = func; + + (void) pop (); + reset_signal_arrived (); + sigset_t this_oldmask = set_process_mask_delta (); + int this_errno = saved_errno; + incyg--; + sig = 0; + sigfunc (thissig); + incyg++; + set_signal_mask (this_oldmask); + if (this_errno >= 0) + set_errno (this_errno); + } + + return this_sa_flags & SA_RESTART; +} + +extern "C" void __stdcall +reset_signal_arrived () +{ + // NEEDED? WaitForSingleObject (signal_arrived, 10); + (void) ResetEvent (signal_arrived); + sigproc_printf ("reset signal_arrived"); + if (_my_tls.stackptr > _my_tls.stack) + debug_printf ("stackptr[-1] %p", _my_tls.stackptr[-1]); +} diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc new file mode 100644 index 00000000000..52a5b5ea521 --- /dev/null +++ b/winsup/cygwin/fork.cc @@ -0,0 +1,740 @@ +/* fork.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygerrno.h" +#include "sigproc.h" +#include "pinfo.h" +#include "cygheap.h" +#include "child_info.h" +#define NEED_VFORK +#include "perthread.h" +#include "perprocess.h" +#include "dll_init.h" +#include "sync.h" +#include "shared_info.h" +#include "cygmalloc.h" +#include "cygthread.h" + +#define NPIDS_HELD 4 + +/* 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 (300 * 1000) /* 300 seconds */ + +#define dll_data_start &_data_start__ +#define dll_data_end &_data_end__ +#define dll_bss_start &_bss_start__ +#define dll_bss_end &_bss_end__ + +void +per_thread::set (void *s) +{ + if (s == PER_THREAD_FORK_CLEAR) + { + tls = TlsAlloc (); + s = NULL; + } + TlsSetValue (get_tls (), s); +} + +static void +stack_base (child_info_fork &ch) +{ + MEMORY_BASIC_INFORMATION m; + memset (&m, 0, sizeof m); + if (!VirtualQuery ((LPCVOID) &m, &m, sizeof m)) + system_printf ("couldn't get memory info, %E"); + + ch.stacktop = m.AllocationBase; + ch.stackbottom = (LPBYTE) m.BaseAddress + m.RegionSize; + ch.stacksize = (DWORD) ch.stackbottom - (DWORD) &m; + debug_printf ("bottom %p, top %p, stack %p, size %d, reserve %d", + ch.stackbottom, ch.stacktop, &m, ch.stacksize, + (DWORD) ch.stackbottom - (DWORD) ch.stacktop); +} + +/* Copy memory from parent to child. + The result is a boolean indicating success. */ + +static int +fork_copy (PROCESS_INFORMATION &pi, const char *what, ...) +{ + va_list args; + char *low; + int pass = 0; + + va_start (args, what); + + while ((low = va_arg (args, char *))) + { + char *high = va_arg (args, char *); + DWORD todo = wincap.chunksize () ?: high - low; + char *here; + + for (here = low; here < high; here += todo) + { + DWORD done = 0; + if (here + todo > high) + todo = high - here; + int res = WriteProcessMemory (pi.hProcess, here, here, todo, &done); + debug_printf ("child handle %p, low %p, high %p, res %d", pi.hProcess, + low, high, res); + if (!res || todo != done) + { + if (!res) + __seterrno (); + /* If this happens then there is a bug in our fork + implementation somewhere. */ + system_printf ("%s pass %d failed, %p..%p, done %d, windows pid %u, %E", + what, pass, low, high, done, pi.dwProcessId); + goto err; + } + } + + pass++; + } + + debug_printf ("done"); + return 1; + + err: + TerminateProcess (pi.hProcess, 1); + set_errno (EAGAIN); + return 0; +} + +/* Wait for child to finish what it's doing and signal us. + 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. */ +static int +sync_with_child (PROCESS_INFORMATION &pi, HANDLE subproc_ready, + bool hang_child, const char *s) +{ + /* We also add the child process handle 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 w4[2]; + + debug_printf ("waiting for child. reason: %s, hang_child %d", s, + hang_child); + w4[1] = pi.hProcess; + w4[0] = subproc_ready; + DWORD rc = WaitForMultipleObjects (2, w4, FALSE, FORK_WAIT_TIMEOUT); + + if (rc == WAIT_OBJECT_0 || + WaitForSingleObject (subproc_ready, 0) == WAIT_OBJECT_0) + /* That's ok */; + else if (rc == WAIT_FAILED || rc == WAIT_TIMEOUT) + { + if (rc != WAIT_FAILED) + system_printf ("WaitForMultipleObjects timed out"); + else + system_printf ("WaitForMultipleObjects failed, %E"); + set_errno (EAGAIN); + syscall_printf ("-1 = fork(), WaitForMultipleObjects failed"); + TerminateProcess (pi.hProcess, 1); + return 0; + } + else + { + /* Child died. Clean up and exit. */ + DWORD errcode; + GetExitCodeProcess (pi.hProcess, &errcode); + /* Fix me. This is not enough. The fork should not be considered + * to have failed if the process was essentially killed by a signal. + */ + if (errcode != STATUS_CONTROL_C_EXIT) + { + system_printf ("child %u(%p) died before initialization with status code %p", + cygwin_pid (pi.dwProcessId), pi.hProcess, errcode); + system_printf ("*** child state %s", s); +#ifdef DEBUGGING + try_to_debug (); +#endif + } + set_errno (EAGAIN); + syscall_printf ("Child died before subproc_ready signalled"); + return 0; + } + + debug_printf ("child signalled me"); + return 1; +} + +static int +resume_child (PROCESS_INFORMATION &pi, HANDLE forker_finished) +{ + SetEvent (forker_finished); + debug_printf ("signalled child"); + return 1; +} + +/* Notify parent that it is time for the next step. + Note that this has to be a macro since the parent may be messing with + our stack. */ +static void __stdcall +sync_with_parent (const char *s, bool hang_self) +{ + debug_printf ("signalling parent: %s", s); + /* Tell our parent we're waiting. */ + if (!SetEvent (fork_info->subproc_ready)) + api_fatal ("fork child - SetEvent for %s failed, %E", s); + if (hang_self) + { + HANDLE h = fork_info->forker_finished; + /* 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. */ + DWORD psync_rc = WaitForSingleObject (h, FORK_WAIT_TIMEOUT); + debug_printf ("awake"); + switch (psync_rc) + { + case WAIT_TIMEOUT: + api_fatal ("WFSO timed out %s", s); + break; + case WAIT_FAILED: + if (GetLastError () == ERROR_INVALID_HANDLE && + WaitForSingleObject (fork_info->forker_finished, 1) != WAIT_FAILED) + break; + api_fatal ("WFSO failed %s, fork_finished %p, %E", s, + fork_info->forker_finished); + break; + default: + debug_printf ("no problems"); + break; + } + } +} + +static int __stdcall +fork_child (HANDLE& hParent, dll *&first_dll, bool& load_dlls) +{ + extern void fixup_timers_after_fork (); + debug_printf ("child is running. pid %d, ppid %d, stack here %p", + myself->pid, myself->ppid, __builtin_frame_address (0)); + + /* Restore the inheritance state as in parent + Don't call setuid here! The flags are already set. */ + cygheap->user.reimpersonate (); + + sync_with_parent ("after longjmp", true); + sigproc_printf ("hParent %p, child 1 first_dll %p, load_dlls %d", hParent, + first_dll, load_dlls); + + /* If we've played with the stack, stacksize != 0. That means that + fork() was invoked from other than the main thread. Make sure that + the threadinfo information is properly set up. */ + if (fork_info->stacksize) + { + _main_tls = &_my_tls; + _main_tls->init_thread (NULL, NULL); + _main_tls->local_clib = *_impure_ptr; + _impure_ptr = &_main_tls->local_clib; + } + +#ifdef DEBUGGING + char c; + if (GetEnvironmentVariable ("FORKDEBUG", &c, 1)) + try_to_debug (); + char buf[80]; + /* This is useful for debugging fork problems. Use gdb to attach to + the pid reported here. */ + if (GetEnvironmentVariable ("CYGWIN_FORK_SLEEP", buf, sizeof (buf))) + { + small_printf ("Sleeping %d after fork, pid %u\n", atoi (buf), GetCurrentProcessId ()); + Sleep (atoi (buf)); + } +#endif + + set_file_api_mode (current_codepage); + + MALLOC_CHECK; + + if (fixup_mmaps_after_fork (hParent)) + api_fatal ("recreate_mmaps_after_fork_failed"); + + + MALLOC_CHECK; + + /* If we haven't dynamically loaded any dlls, just signal + the parent. Otherwise, load all the dlls, tell the parent + that we're done, and wait for the parent to fill in the. + loaded dlls' data/bss. */ + if (!load_dlls) + { + cygheap->fdtab.fixup_after_fork (hParent); + ProtectHandleINH (hParent); + sync_with_parent ("performed fork fixup", false); + } + else + { + dlls.load_after_fork (hParent, first_dll); + cygheap->fdtab.fixup_after_fork (hParent); + ProtectHandleINH (hParent); + sync_with_parent ("loaded dlls", true); + } + + ForceCloseHandle (hParent); + (void) ForceCloseHandle1 (fork_info->subproc_ready, subproc_ready); + (void) ForceCloseHandle1 (fork_info->forker_finished, forker_finished); + + pinfo_fixup_after_fork (); + _my_tls.fixup_after_fork (); + sigproc_init (); + +#ifdef USE_SERVER + if (fixup_shms_after_fork ()) + api_fatal ("recreate_shm areas after fork failed"); +#endif + + /* Set thread local stuff to zero. Under Windows 95/98 this is sometimes + non-zero, for some reason. + FIXME: There is a memory leak here after a fork. */ + for (per_thread **t = threadstuff; *t; t++) + if ((*t)->clear_on_fork ()) + (*t)->set (); + + pthread::atforkchild (); + fixup_timers_after_fork (); + cygbench ("fork-child"); + cygwin_finished_initializing = true; + return 0; +} + +#ifndef NO_SLOW_PID_REUSE +static void +slow_pid_reuse (HANDLE h) +{ + static NO_COPY HANDLE last_fork_procs[NPIDS_HELD] = {0}; + static NO_COPY unsigned nfork_procs = 0; + + if (nfork_procs >= (sizeof (last_fork_procs) / sizeof (last_fork_procs [0]))) + nfork_procs = 0; + /* Keep a list of handles to forked processes sitting around to prevent + Windows from reusing the same pid n times in a row. Having the same pids + close in succesion confuses bash. Keeping a handle open will stop + windows from reusing the same pid. */ + if (last_fork_procs[nfork_procs]) + ForceCloseHandle1 (last_fork_procs[nfork_procs], fork_stupidity); + if (DuplicateHandle (hMainProc, h, hMainProc, &last_fork_procs[nfork_procs], + 0, FALSE, DUPLICATE_SAME_ACCESS)) + ProtectHandle1 (last_fork_procs[nfork_procs], fork_stupidity); + else + { + last_fork_procs[nfork_procs] = NULL; + system_printf ("couldn't create last_fork_proc, %E"); + } + nfork_procs++; +} +#endif + +static int __stdcall +fork_parent (HANDLE& hParent, dll *&first_dll, + bool& load_dlls, void *stack_here, child_info_fork &ch) +{ + HANDLE subproc_ready, forker_finished; + DWORD rc; + PROCESS_INFORMATION pi = {0, NULL, 0, 0}; + + pthread::atforkprepare (); + + int c_flags = GetPriorityClass (hMainProc) /*| + CREATE_NEW_PROCESS_GROUP*/; + STARTUPINFO si = {0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}; + + /* If we don't have a console, then don't create a console for the + child either. */ + HANDLE console_handle = CreateFile ("CONOUT$", GENERIC_WRITE, + FILE_SHARE_WRITE, &sec_none_nih, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL); + + if (console_handle != INVALID_HANDLE_VALUE) + CloseHandle (console_handle); + else + c_flags |= DETACHED_PROCESS; + + /* Some file types (currently only sockets) need extra effort in the + parent after CreateProcess and before copying the datastructures + to the child. So we have to start the child in suspend state, + unfortunately, to avoid a race condition. */ + if (cygheap->fdtab.need_fixup_before ()) + c_flags |= CREATE_SUSPENDED; + + /* Create an inheritable handle to pass to the child process. This will + allow the child to duplicate handles from the parent to itself. */ + hParent = NULL; + if (!DuplicateHandle (hMainProc, hMainProc, hMainProc, &hParent, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + { + system_printf ("couldn't create handle to myself for child, %E"); + return -1; + } + + /* Remember the address of the first loaded dll and decide + if we need to load dlls. We do this here so that this + information will be available in the parent and, when + the stack is copied, in the child. */ + first_dll = dlls.start.next; + load_dlls = dlls.reload_on_fork && dlls.loaded_dlls; + + /* This will help some of the confusion. */ + fflush (stdout); + + subproc_ready = CreateEvent (&sec_all, FALSE, FALSE, NULL); + if (subproc_ready == NULL) + { + CloseHandle (hParent); + system_printf ("unable to allocate subproc_ready event, %E"); + return -1; + } + forker_finished = CreateEvent (&sec_all, FALSE, FALSE, NULL); + if (forker_finished == NULL) + { + CloseHandle (hParent); + CloseHandle (subproc_ready); + system_printf ("unable to allocate forker_finished event, %E"); + return -1; + } + + ProtectHandleINH (subproc_ready); + ProtectHandleINH (forker_finished); + + init_child_info (PROC_FORK, &ch, subproc_ready); + + ch.forker_finished = forker_finished; + + stack_base (ch); + + si.cb = sizeof (STARTUPINFO); + si.lpReserved2 = (LPBYTE)&ch; + si.cbReserved2 = sizeof (ch); + + /* Remove impersonation */ + cygheap->user.deimpersonate (); + + ch.parent = hParent; + + syscall_printf ("CreateProcess (%s, %s, 0, 0, 1, %x, 0, 0, %p, %p)", + myself->progname, myself->progname, c_flags, &si, &pi); + bool locked = __malloc_lock (); + void *newheap; + newheap = cygheap_setup_for_child (&ch, cygheap->fdtab.need_fixup_before ()); + rc = CreateProcess (myself->progname, /* image to run */ + myself->progname, /* what we send in arg0 */ + &sec_none_nih, + &sec_none_nih, + TRUE, /* inherit handles from parent */ + c_flags, + NULL, /* environment filled in later */ + 0, /* use current drive/directory */ + &si, + &pi); + + CloseHandle (hParent); + + if (!rc) + { + __seterrno (); + syscall_printf ("CreateProcessA failed, %E"); + ForceCloseHandle (subproc_ready); + ForceCloseHandle (forker_finished); + /* Restore impersonation */ + cygheap->user.reimpersonate (); + cygheap_setup_for_child_cleanup (newheap, &ch, 0); + __malloc_unlock (); + return -1; + } + + /* Fixup the parent datastructure if needed and resume the child's + main thread. */ + if (!cygheap->fdtab.need_fixup_before ()) + cygheap_setup_for_child_cleanup (newheap, &ch, 0); + else + { + cygheap->fdtab.fixup_before_fork (pi.dwProcessId); + cygheap_setup_for_child_cleanup (newheap, &ch, 1); + ResumeThread (pi.hThread); + } + + int forked_pid = cygwin_pid (pi.dwProcessId); + pinfo forked (forked_pid, 1); + + if (!forked) + { + syscall_printf ("pinfo failed"); + if (get_errno () != ENOMEM) + set_errno (EAGAIN); + goto cleanup; + } + + /* Initialize things that are done later in dll_crt0_1 that aren't done + for the forkee. */ + strcpy (forked->progname, myself->progname); + + /* Restore impersonation */ + cygheap->user.reimpersonate (); + + ProtectHandle (pi.hThread); + /* Protect the handle but name it similarly to the way it will + be called in subproc handling. */ + ProtectHandle1 (pi.hProcess, childhProc); + + /* Fill in fields in the child's process table entry. */ + forked->dwProcessId = pi.dwProcessId; + forked.hProcess = pi.hProcess; + + /* Hopefully, this will succeed. The alternative to doing things this + way is to reserve space prior to calling CreateProcess and then fill + it in afterwards. This requires more bookkeeping than I like, though, + so we'll just do it the easy way. So, terminate any child process if + we can't actually record the pid in the internal table. */ + if (!forked.remember ()) + { + TerminateProcess (pi.hProcess, 1); + set_errno (EAGAIN); + goto cleanup; + } + +#ifndef NO_SLOW_PID_REUSE + slow_pid_reuse (pi.hProcess); +#endif + + /* Wait for subproc to initialize itself. */ + if (!sync_with_child (pi, subproc_ready, true, "waiting for longjmp")) + goto cleanup; + + /* CHILD IS STOPPED */ + debug_printf ("child is alive (but stopped)"); + + /* Initialize, in order: data, bss, heap, stack, dll data, dll bss + Note: variables marked as NO_COPY will not be copied + since they are placed in a protected segment. */ + + + MALLOC_CHECK; + void *impure_beg; + void *impure_end; + if (&_my_tls == _main_tls) + impure_beg = impure_end = NULL; + else + { + impure_beg = _impure_ptr; + impure_end = _impure_ptr + 1; + } + rc = fork_copy (pi, "user/cygwin data", + user_data->data_start, user_data->data_end, + user_data->bss_start, user_data->bss_end, + cygheap->user_heap.base, cygheap->user_heap.ptr, + stack_here, ch.stackbottom, + dll_data_start, dll_data_end, + dll_bss_start, dll_bss_end, impure_beg, impure_end, NULL); + + __malloc_unlock (); + locked = false; + MALLOC_CHECK; + if (!rc) + goto cleanup; + + /* Now fill data/bss of any DLLs that were linked into the program. */ + for (dll *d = dlls.istart (DLL_LINK); d; d = dlls.inext ()) + { + debug_printf ("copying data/bss of a linked dll"); + if (!fork_copy (pi, "linked dll data/bss", d->p.data_start, d->p.data_end, + d->p.bss_start, d->p.bss_end, + NULL)) + goto cleanup; + } + + /* Start thread, and wait for it to reload dlls. */ + if (!resume_child (pi, forker_finished) || + !sync_with_child (pi, subproc_ready, load_dlls, "child loading dlls")) + goto cleanup; + + /* If DLLs were loaded in the parent, then the child has reloaded all + of them and is now waiting to have all of the individual data and + bss sections filled in. */ + if (load_dlls) + { + /* CHILD IS STOPPED */ + /* write memory of reloaded dlls */ + for (dll *d = dlls.istart (DLL_LOAD); d; d = dlls.inext ()) + { + debug_printf ("copying data/bss for a loaded dll"); + if (!fork_copy (pi, "loaded dll data/bss", d->p.data_start, d->p.data_end, + d->p.bss_start, d->p.bss_end, + NULL)) + goto cleanup; + } + /* Start the child up again. */ + (void) resume_child (pi, forker_finished); + } + + if (pi.hProcess) + ForceCloseHandle1 (pi.hProcess, childhProc); + ForceCloseHandle (subproc_ready); + ForceCloseHandle (pi.hThread); + ForceCloseHandle (forker_finished); + forker_finished = NULL; + pi.hThread = NULL; + pthread::atforkparent (); + + return forked_pid; + +/* Common cleanup code for failure cases */ + cleanup: + if (locked) + __malloc_unlock (); + + /* Remember to de-allocate the fd table. */ + if (pi.hProcess) + ForceCloseHandle1 (pi.hProcess, childhProc); + if (pi.hThread) + ForceCloseHandle (pi.hThread); + if (subproc_ready) + ForceCloseHandle (subproc_ready); + if (forker_finished) + ForceCloseHandle (forker_finished); + return -1; +} + +extern "C" int +fork () +{ + struct + { + HANDLE hParent; + dll *first_dll; + bool load_dlls; + } grouped; + + MALLOC_CHECK; + + debug_printf ("entering"); + grouped.hParent = grouped.first_dll = NULL; + grouped.load_dlls = 0; + + void *esp; + __asm__ volatile ("movl %%esp,%0": "=r" (esp)); + + myself->set_has_pgid_children (); + + child_info_fork ch; + + sig_send (NULL, __SIGHOLD); + int res = setjmp (ch.jmp); + if (res) + res = fork_child (grouped.hParent, grouped.first_dll, grouped.load_dlls); + else + res = fork_parent (grouped.hParent, grouped.first_dll, grouped.load_dlls, esp, ch); + sig_send (NULL, __SIGNOHOLD); + + MALLOC_CHECK; + syscall_printf ("%d = fork()", res); + return res; +} +#ifdef DEBUGGING +void +fork_init () +{ +} +#endif /*DEBUGGING*/ + +#ifdef NEWVFORK +/* Dummy function to force second assignment below to actually be + carried out */ +static vfork_save * +get_vfork_val () +{ + return vfork_storage.val (); +} +#endif + +extern "C" int +vfork () +{ +#ifndef NEWVFORK + debug_printf ("stub called"); + return fork (); +#else + vfork_save *vf = get_vfork_val (); + char **esp, **pp; + + if (vf == NULL) + vf = vfork_storage.create (); + else if (vf->pid) + return fork (); + + // FIXME the tls stuff could introduce a signal race if a child process + // exits quickly. + if (!setjmp (vf->j)) + { + vf->pid = -1; + __asm__ volatile ("movl %%esp,%0": "=r" (vf->vfork_esp):); + __asm__ volatile ("movl %%ebp,%0": "=r" (vf->vfork_ebp):); + for (pp = (char **) vf->frame, esp = vf->vfork_esp; + esp <= vf->vfork_ebp + 2; pp++, esp++) + *pp = *esp; + vf->ctty = myself->ctty; + vf->sid = myself->sid; + vf->pgid = myself->pgid; + cygheap->ctty_on_hold = cygheap->ctty; + vf->open_fhs = cygheap->open_fhs; + debug_printf ("cygheap->ctty_on_hold %p, cygheap->open_fhs %d", cygheap->ctty_on_hold, cygheap->open_fhs); + int res = cygheap->fdtab.vfork_child_dup () ? 0 : -1; + debug_printf ("%d = vfork()", res); + _my_tls.call_signal_handler (); // FIXME: racy + vf->tls = _my_tls; + return res; + } + + vf = get_vfork_val (); + + for (pp = (char **) vf->frame, esp = vf->vfork_esp; + esp <= vf->vfork_ebp + 2; pp++, esp++) + *esp = *pp; + + cygheap->fdtab.vfork_parent_restore (); + + myself->ctty = vf->ctty; + myself->sid = vf->sid; + myself->pgid = vf->pgid; + termios_printf ("cygheap->ctty %p, cygheap->ctty_on_hold %p", cygheap->ctty, cygheap->ctty_on_hold); + cygheap->open_fhs = vf->open_fhs; + + if (vf->pid < 0) + { + int exitval = vf->exitval; + vf->pid = 0; + if ((vf->pid = fork ()) == 0) + exit (exitval); + } + + int pid = vf->pid; + vf->pid = 0; + debug_printf ("exiting vfork, pid %d", pid); + sig_dispatch_pending (); + + _my_tls.call_signal_handler (); // FIXME: racy + _my_tls = vf->tls; + return pid; +#endif +} diff --git a/winsup/cygwin/include/sys/cygwin.h b/winsup/cygwin/include/sys/cygwin.h new file mode 100644 index 00000000000..b8c99738e48 --- /dev/null +++ b/winsup/cygwin/include/sys/cygwin.h @@ -0,0 +1,265 @@ +/* sys/cygwin.h + + Copyright 1997, 1998, 2000, 2001, 2002 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef _SYS_CYGWIN_H +#define _SYS_CYGWIN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern pid_t cygwin32_winpid_to_pid (int); +extern void cygwin32_win32_to_posix_path_list (const char *, char *); +extern int cygwin32_win32_to_posix_path_list_buf_size (const char *); +extern void cygwin32_posix_to_win32_path_list (const char *, char *); +extern int cygwin32_posix_to_win32_path_list_buf_size (const char *); +extern int cygwin32_conv_to_win32_path (const char *, char *); +extern int cygwin32_conv_to_full_win32_path (const char *, char *); +extern void cygwin32_conv_to_posix_path (const char *, char *); +extern void cygwin32_conv_to_full_posix_path (const char *, char *); +extern int cygwin32_posix_path_list_p (const char *); +extern void cygwin32_split_path (const char *, char *, char *); + +extern pid_t cygwin_winpid_to_pid (int); +extern int cygwin_win32_to_posix_path_list (const char *, char *); +extern int cygwin_win32_to_posix_path_list_buf_size (const char *); +extern int cygwin_posix_to_win32_path_list (const char *, char *); +extern int cygwin_posix_to_win32_path_list_buf_size (const char *); +extern int cygwin_conv_to_win32_path (const char *, char *); +extern int cygwin_conv_to_full_win32_path (const char *, char *); +extern int cygwin_conv_to_posix_path (const char *, char *); +extern int cygwin_conv_to_full_posix_path (const char *, char *); +extern int cygwin_posix_path_list_p (const char *); +extern void cygwin_split_path (const char *, char *, char *); + +struct __cygwin_perfile +{ + const char *name; + unsigned flags; +}; + +/* External interface stuff */ + +typedef enum + { + CW_LOCK_PINFO, + CW_UNLOCK_PINFO, + CW_GETTHREADNAME, + CW_GETPINFO, + CW_SETPINFO, + CW_SETTHREADNAME, + CW_GETVERSIONINFO, + CW_READ_V1_MOUNT_TABLES, + CW_USER_DATA, + CW_PERFILE, + CW_GET_CYGDRIVE_PREFIXES, + CW_GETPINFO_FULL, + CW_INIT_EXCEPTIONS, + CW_GET_CYGDRIVE_INFO, + CW_SET_CYGWIN_REGISTRY_NAME, + CW_GET_CYGWIN_REGISTRY_NAME, + CW_STRACE_TOGGLE, + CW_STRACE_ACTIVE, + CW_CYGWIN_PID_TO_WINPID, + CW_EXTRACT_DOMAIN_AND_USER, + CW_CMDLINE, + CW_CHECK_NTSEC, + CW_GET_ERRNO_FROM_WINERROR, + CW_GET_POSIX_SECURITY_ATTRIBUTE, + CW_GET_SHMLBA, + CW_GET_UID_FROM_SID, + CW_GET_GID_FROM_SID, + CW_GET_BINMODE + } cygwin_getinfo_types; + +#define CW_NEXTPID 0x80000000 /* or with pid to get next one */ +unsigned long cygwin_internal (cygwin_getinfo_types, ...); + +/* Flags associated with process_state */ +enum +{ + PID_IN_USE = 0x0001, /* Entry in use. */ + PID_ZOMBIE = 0x0002, /* Child exited: no parent wait. */ + PID_STOPPED = 0x0004, /* Waiting for SIGCONT. */ + PID_TTYIN = 0x0008, /* Waiting for terminal input. */ + PID_TTYOU = 0x0010, /* Waiting for terminal output. */ + PID_ORPHANED = 0x0020, /* Member of an orphaned process group. */ + PID_ACTIVE = 0x0040, /* Pid accepts signals. */ + PID_CYGPARENT = 0x0080, /* Set if parent was a cygwin app. */ + PID_MAP_RW = 0x0100, /* Flag to open map rw. */ + PID_MYSELF = 0x0200, /* Flag that pid is me. */ + PID_NOCLDSTOP = 0x0400, /* Set if no SIGCHLD signal on stop. */ + PID_INITIALIZING = 0x0800, /* Set until ready to receive signals. */ + PID_USETTY = 0x1000, /* Setting this enables or disables cygwin's */ + /* tty support. This is inherited by */ + /* all execed or forked processes. */ + PID_ALLPIDS = 0x2000, /* child has execed */ + PID_EXECED = 0x4000, /* redirect to original pid info block */ + PID_NOREDIR = 0x8000, /* don't redirect if execed */ + PID_EXITED = 0x80000000 /* Free entry. */ +}; + +#ifdef WINVER +#ifdef _PATH_PASSWD +extern HANDLE cygwin_logon_user (const struct passwd *, const char *); +#endif + +/* This lives in the app and is initialized before jumping into the DLL. + It should only contain stuff which the user's process needs to see, or + which is needed before the user pointer is initialized, or is needed to + carry inheritance information from parent to child. Note that it cannot + be used to carry inheritance information across exec! + + Remember, this structure is linked into the application's executable. + Changes to this can invalidate existing executables, so we go to extra + lengths to avoid having to do it. + + When adding/deleting members, remember to adjust {public,internal}_reserved. + The size of the class shouldn't change [unless you really are prepared to + invalidate all existing executables]. The program does a check (using + SIZEOF_PER_PROCESS) to make sure you remember to make the adjustment. +*/ + +#ifdef __cplusplus +class ResourceLocks; +class MTinterface; +#endif + +struct per_process +{ + char *initial_sp; + + /* The offset of these 3 values can never change. */ + /* magic_biscuit is the size of this class and should never change. */ + unsigned long magic_biscuit; + unsigned long dll_major; + unsigned long dll_minor; + + struct _reent **impure_ptr_ptr; + char ***envptr; + + /* Used to point to the memory machine we should use. Usually these + point back into the dll, but they can be overridden by the user. */ + void *(*malloc)(size_t); + void (*free)(void *); + void *(*realloc)(void *, size_t); + + int *fmode_ptr; + + int (*main)(int, char **, char **); + void (**ctors)(void); + void (**dtors)(void); + + /* For fork */ + void *data_start; + void *data_end; + void *bss_start; + void *bss_end; + + void *(*calloc)(size_t, size_t); + /* For future expansion of values set by the app. */ + void (*premain[4]) (int, char **, struct per_process *); + + /* The rest are *internal* to cygwin.dll. + Those that are here because we want the child to inherit the value from + the parent (which happens when bss is copied) are marked as such. */ + + /* non-zero of ctors have been run. Inherited from parent. */ + int run_ctors_p; + + DWORD unused[7]; + + /* Non-zero means the task was forked. The value is the pid. + Inherited from parent. */ + int forkee; + + HMODULE hmodule; + + DWORD api_major; /* API version that this program was */ + DWORD api_minor; /* linked with */ + /* For future expansion, so apps won't have to be relinked if we + add an item. */ + DWORD unused2[5]; + +#if defined (__INSIDE_CYGWIN__) && defined (__cplusplus) + ResourceLocks *resourcelocks; + MTinterface *threadinterface; +#else + void *resourcelocks; + void *threadinterface; +#endif + struct _reent *impure_ptr; +}; +#define per_process_overwrite ((unsigned) &(((struct per_process *) NULL)->resourcelocks)) + +extern void cygwin_premain0 (int argc, char **argv, struct per_process *); +extern void cygwin_premain1 (int argc, char **argv, struct per_process *); +extern void cygwin_premain2 (int argc, char **argv, struct per_process *); +extern void cygwin_premain3 (int argc, char **argv, struct per_process *); + +extern void cygwin_set_impersonation_token (const HANDLE); + +/* included if is included */ +extern int cygwin32_attach_handle_to_fd (char *, int, HANDLE, mode_t, DWORD); +extern int cygwin_attach_handle_to_fd (char *, int, HANDLE, mode_t, DWORD); + +#ifdef __CYGWIN__ +#include + +#define TTY_CONSOLE 0x40000000 + +#define EXTERNAL_PINFO_VERSION_16_BIT 0 +#define EXTERNAL_PINFO_VERSION_32_BIT 1 +#define EXTERNAL_PINFO_VERSION EXTERNAL_PINFO_VERSION_32_BIT + +#ifndef _SYS_TYPES_H +typedef unsigned short __uid16_t; +typedef unsigned short __gid16_t; +typedef unsigned long __uid32_t; +typedef unsigned long __gid32_t; +#endif + +struct external_pinfo + { + pid_t pid; + pid_t ppid; + DWORD exitcode; + DWORD dwProcessId, dwSpawnedProcessId; + __uid16_t uid; + __gid16_t gid; + pid_t pgid; + pid_t sid; + int ctty; + mode_t umask; + + long start_time; + struct rusage rusage_self; + struct rusage rusage_children; + + char progname[MAX_PATH]; + + DWORD strace_mask; + DWORD version; + + DWORD process_state; + + /* Only available if version >= EXTERNAL_PINFO_VERSION_32_BIT */ + __uid32_t uid32; + __gid32_t gid32; +}; +#endif /*__CYGWIN__*/ +#endif /*WINVER*/ + +#ifdef __cplusplus +}; +#endif +#endif /* _SYS_CYGWIN_H */ diff --git a/winsup/cygwin/include/sys/wait.h b/winsup/cygwin/include/sys/wait.h new file mode 100644 index 00000000000..d0708383f7d --- /dev/null +++ b/winsup/cygwin/include/sys/wait.h @@ -0,0 +1,74 @@ +/* sys/wait.h + + Copyright 1997, 1998, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef _SYS_WAIT_H +#define _SYS_WAIT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WNOHANG 1 +#define WUNTRACED 2 + +/* A status looks like: + <2 bytes info> <2 bytes code> + + == 0, child has exited, info is the exit value + == 1..7e, child has exited, info is the signal number. + == 7f, child has stopped, info was the signal number. + == 80, there was a core dump. +*/ + +#define WIFEXITED(w) (((w) & 0xff) == 0) +#define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f)) +#define WIFSTOPPED(w) (((w) & 0xff) == 0x7f) +#define WEXITSTATUS(w) (((w) >> 8) & 0xff) +#define WTERMSIG(w) ((w) & 0x7f) +#define WSTOPSIG WEXITSTATUS +#define WCOREDUMP(w) (WIFSIGNALED(w) && (w & 0x80)) + +pid_t wait (int *); +pid_t waitpid (pid_t, int *, int); +pid_t wait3 (int *__status, int __options, struct rusage *__rusage); +pid_t wait4 (pid_t __pid, int *__status, int __options, struct rusage *__rusage); + +union wait + { + int w_status; + struct + { + unsigned int __w_termsig:7; /* Terminating signal. */ + unsigned int __w_coredump:1; /* Set if dumped core. */ + unsigned int __w_retcode:8; /* Return code if exited normally. */ + unsigned int:16; + } __wait_terminated; + struct + { + unsigned int __w_stopval:8; /* W_STOPPED if stopped. */ + unsigned int __w_stopsig:8; /* Stopping signal. */ + unsigned int:16; + } __wait_stopped; + }; + +#define w_termsig __wait_terminated.__w_termsig +#define w_coredump __wait_terminated.__w_coredump +#define w_retcode __wait_terminated.__w_retcode +#define w_stopsig __wait_stopped.__w_stopsig +#define w_stopval __wait_stopped.__w_stopval + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/winsup/cygwin/pinfo.cc b/winsup/cygwin/pinfo.cc new file mode 100644 index 00000000000..f11e1c5558f --- /dev/null +++ b/winsup/cygwin/pinfo.cc @@ -0,0 +1,981 @@ +/* pinfo.cc: process table support + + Copyright 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygerrno.h" +#include "sigproc.h" +#include "pinfo.h" +#include "cygwin_version.h" +#include "perprocess.h" +#include "environ.h" +#include +#include +#include +#include "ntdll.h" +#include "cygthread.h" +#include "shared_info.h" +#include "cygheap.h" +#include "fhandler.h" +#include "cygmalloc.h" +#include "cygtls.h" + +static char NO_COPY pinfo_dummy[sizeof (_pinfo)] = {0}; + +pinfo NO_COPY myself ((_pinfo *)&pinfo_dummy); // Avoid myself != NULL checks + +HANDLE hexec_proc; + +void __stdcall +pinfo_fixup_after_fork () +{ + if (hexec_proc) + CloseHandle (hexec_proc); + /* Keeps the cygpid from being reused. No rights required */ + if (!DuplicateHandle (hMainProc, hMainProc, hMainProc, &hexec_proc, 0, + TRUE, 0)) + { + system_printf ("couldn't save current process handle %p, %E", hMainProc); + hexec_proc = NULL; + } + VerifyHandle (hexec_proc); +} + +/* Initialize the process table. + This is done once when the dll is first loaded. */ + +void __stdcall +set_myself (HANDLE h) +{ + if (!h) + cygheap->pid = cygwin_pid (GetCurrentProcessId ()); + myself.init (cygheap->pid, PID_IN_USE | PID_MYSELF, h); + myself->process_state |= PID_IN_USE; + myself->start_time = time (NULL); /* Register our starting time. */ + + (void) GetModuleFileName (NULL, myself->progname, sizeof (myself->progname)); + if (!strace.active) + strace.hello (); + debug_printf ("myself->dwProcessId %u", myself->dwProcessId); + InitializeCriticalSection (&myself.lock); + if (!h && myself->ppid) + { + pinfo parent (myself->ppid); + if (parent && parent->wr_proc_pipe) + CloseHandle (parent->wr_proc_pipe); + } + return; +} + +/* Initialize the process table entry for the current task. + This is not called for forked tasks, only execed ones. */ +void __stdcall +pinfo_init (char **envp, int envc) +{ + if (envp) + { + environ_init (envp, envc); + /* spawn has already set up a pid structure for us so we'll use that */ + myself->process_state |= PID_CYGPARENT; + } + else + { + /* Invent our own pid. */ + + set_myself (NULL); + myself->ppid = 1; + myself->pgid = myself->sid = myself->pid; + myself->ctty = -1; + myself->uid = ILLEGAL_UID; + myself->gid = UNKNOWN_GID; + environ_init (NULL, 0); /* call after myself has been set up */ + } + + debug_printf ("pid %d, pgid %d", myself->pid, myself->pgid); +} + +void +_pinfo::exit (UINT n, bool norecord) +{ + exit_state = ES_FINAL; + cygthread::terminate (); + if (norecord) + sigproc_terminate (); + else + exitcode = n; + if (this) + { + if (!norecord) + process_state = PID_EXITED; + + /* FIXME: There is a potential race between an execed process and its + parent here. I hated to add a mutex just for this, though. */ + struct rusage r; + fill_rusage (&r, hMainProc); + add_rusage (&rusage_self, &r); + } + + sigproc_printf ("Calling ExitProcess %d", n); + _my_tls.stacklock = 0; + _my_tls.stackptr = _my_tls.stack; + ExitProcess (n); +} + +void +pinfo::init (pid_t n, DWORD flag, HANDLE in_h) +{ + if (myself && n == myself->pid) + { + procinfo = myself; + destroy = 0; + h = NULL; + return; + } + + void *mapaddr; + if (!(flag & PID_MYSELF)) + mapaddr = NULL; + else + { + flag &= ~PID_MYSELF; + HANDLE hdummy; + mapaddr = open_shared (NULL, 0, hdummy, 0, SH_MYSELF); + } + + int createit = flag & (PID_IN_USE | PID_EXECED); + DWORD access = FILE_MAP_READ + | (flag & (PID_IN_USE | PID_EXECED | PID_MAP_RW) ? FILE_MAP_WRITE : 0); + for (int i = 0; i < 10; i++) + { + int created; + char mapname[CYG_MAX_PATH]; /* XXX Not a path */ + shared_name (mapname, "cygpid", n); + + int mapsize; + if (flag & PID_EXECED) + mapsize = PINFO_REDIR_SIZE; + else + mapsize = sizeof (_pinfo); + + if (in_h) + { + h = in_h; + created = 0; + } + else if (!createit) + { + h = OpenFileMapping (access, FALSE, mapname); + created = 0; + } + else + { + char sa_buf[1024]; + PSECURITY_ATTRIBUTES sec_attribs = + sec_user_nih (sa_buf, cygheap->user.sid(), well_known_world_sid, + FILE_MAP_READ); + h = CreateFileMapping (INVALID_HANDLE_VALUE, sec_attribs, + PAGE_READWRITE, 0, mapsize, mapname); + created = h && GetLastError () != ERROR_ALREADY_EXISTS; + } + + if (!h) + { + if (createit) + __seterrno (); + procinfo = NULL; + return; + } + + procinfo = (_pinfo *) MapViewOfFileEx (h, access, 0, 0, 0, mapaddr); + if (procinfo) + /* it worked */; + else if (exit_state) + return; /* exiting */ + else + { + if (GetLastError () == ERROR_INVALID_HANDLE) + api_fatal ("MapViewOfFileEx(%p, in_h %p) failed, %E", h, in_h); + else + { + debug_printf ("MapViewOfFileEx(%p, in_h %p) failed, %E", h, in_h); + CloseHandle (h); + } + if (i < 9) + continue; + else + return; + } + + ProtectHandle1 (h, pinfo_shared_handle); + + if ((procinfo->process_state & PID_INITIALIZING) && (flag & PID_NOREDIR) + && cygwin_pid (procinfo->dwProcessId) != procinfo->pid) + { + release (); + set_errno (ENOENT); + return; + } + + if (procinfo->process_state & PID_EXECED) + { + assert (!i); + pid_t realpid = procinfo->pid; + debug_printf ("execed process windows pid %d, cygwin pid %d", n, realpid); + if (realpid == n) + api_fatal ("retrieval of execed process info for pid %d failed due to recursion.", n); + n = realpid; + release (); + if (flag & PID_ALLPIDS) + { + set_errno (ENOENT); + break; + } + continue; + } + + /* In certain rare, pathological cases, it is possible for the shared + memory region to exist for a while after a process has exited. This + should only be a brief occurrence, so rather than introduce some kind + of locking mechanism, just loop. FIXME: I'm sure I'll regret doing it + this way at some point. */ + if (i < 9 && !created && createit && (procinfo->process_state & PID_EXITED)) + { + low_priority_sleep (5); + release (); + continue; + } + + if (!created) + /* nothing */; + else if (!(flag & PID_EXECED)) + procinfo->pid = n; + else + { + procinfo->process_state |= PID_IN_USE | PID_EXECED; + procinfo->pid = myself->pid; + } + + break; + } + destroy = 1; +} + +void +pinfo::set_acl() +{ + char sa_buf[1024]; + SECURITY_DESCRIPTOR sd; + + sec_acl ((PACL) sa_buf, true, true, cygheap->user.sid (), + well_known_world_sid, FILE_MAP_READ); + if (!InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION)) + debug_printf ("InitializeSecurityDescriptor %E"); + else if (!SetSecurityDescriptorDacl (&sd, TRUE, (PACL) sa_buf, FALSE)) + debug_printf ("SetSecurityDescriptorDacl %E"); + else if (!SetKernelObjectSecurity (h, DACL_SECURITY_INFORMATION, &sd)) + debug_printf ("SetKernelObjectSecurity %E"); +} + +void +_pinfo::set_ctty (tty_min *tc, int flags, fhandler_tty_slave *arch) +{ + debug_printf ("checking if /dev/tty%d changed", ctty); + if ((ctty < 0 || ctty == tc->ntty) && !(flags & O_NOCTTY)) + { + ctty = tc->ntty; + syscall_printf ("attached tty%d sid %d, pid %d, tty->pgid %d, tty->sid %d", + tc->ntty, sid, pid, pgid, tc->getsid ()); + + pinfo p (tc->getsid ()); + if (sid == pid && (!p || p->pid == pid || !proc_exists (p))) + { + paranoid_printf ("resetting tty%d sid. Was %d, now %d. pgid was %d, now %d.", + tc->ntty, tc->getsid (), sid, tc->getpgid (), pgid); + /* We are the session leader */ + tc->setsid (sid); + tc->setpgid (pgid); + } + else + sid = tc->getsid (); + if (tc->getpgid () == 0) + tc->setpgid (pgid); + if (cygheap->ctty != arch) + { + debug_printf ("cygheap->ctty %p, arch %p", cygheap->ctty, arch); + if (!cygheap->ctty) + syscall_printf ("ctty NULL"); + else + { + syscall_printf ("ctty %p, usecount %d", cygheap->ctty, + cygheap->ctty->usecount); + cygheap->ctty->close (); + } + cygheap->ctty = arch; + if (arch) + { + arch->usecount++; + cygheap->open_fhs++; + report_tty_counts (cygheap->ctty, "ctty", "incremented ", ""); + } + } + } +} + +bool +_pinfo::alive () +{ + HANDLE h = OpenProcess (PROCESS_QUERY_INFORMATION, false, dwProcessId); + if (h) + CloseHandle (h); + return !!h; +} + +extern char **__argv; + +void +_pinfo::commune_recv () +{ + DWORD nr; + DWORD code; + HANDLE hp; + HANDLE __fromthem = NULL; + HANDLE __tothem = NULL; + + hp = OpenProcess (PROCESS_DUP_HANDLE, false, dwProcessId); + if (!hp) + { + sigproc_printf ("couldn't open handle for pid %d(%u)", pid, dwProcessId); + hello_pid = -1; + return; + } + if (!DuplicateHandle (hp, fromthem, hMainProc, &__fromthem, 0, false, DUPLICATE_SAME_ACCESS)) + { + sigproc_printf ("couldn't duplicate fromthem, %E"); + CloseHandle (hp); + hello_pid = -1; + return; + } + + if (!DuplicateHandle (hp, tothem, hMainProc, &__tothem, 0, false, DUPLICATE_SAME_ACCESS)) + { + sigproc_printf ("couldn't duplicate tothem, %E"); + CloseHandle (__fromthem); + CloseHandle (hp); + hello_pid = -1; + return; + } + + hello_pid = 0; + + if (!ReadFile (__fromthem, &code, sizeof code, &nr, NULL) || nr != sizeof code) + { + CloseHandle (hp); + /* __seterrno ();*/ // this is run from the signal thread, so don't set errno + goto out; + } + + switch (code) + { + case PICOM_CMDLINE: + { + unsigned n = 1; + CloseHandle (__fromthem); __fromthem = NULL; + extern int __argc_safe; + const char *argv[__argc_safe + 1]; + + CloseHandle (hp); + for (int i = 0; i < __argc_safe; i++) + { + if (IsBadStringPtr (__argv[i], INT32_MAX)) + argv[i] = ""; + else + argv[i] = __argv[i]; + n += strlen (argv[i]) + 1; + } + argv[__argc_safe] = NULL; + if (!WriteFile (__tothem, &n, sizeof n, &nr, NULL)) + { + /*__seterrno ();*/ // this is run from the signal thread, so don't set errno + sigproc_printf ("WriteFile sizeof argv failed, %E"); + } + else + for (const char **a = argv; *a; a++) + if (!WriteFile (__tothem, *a, strlen (*a) + 1, &nr, NULL)) + { + sigproc_printf ("WriteFile arg %d failed, %E", a - argv); + break; + } + if (!WriteFile (__tothem, "", 1, &nr, NULL)) + { + sigproc_printf ("WriteFile null failed, %E"); + break; + } + break; + } + case PICOM_FIFO: + { + char path[CYG_MAX_PATH + 1]; + unsigned len; + if (!ReadFile (__fromthem, &len, sizeof len, &nr, NULL) + || nr != sizeof len) + { + CloseHandle (hp); + /* __seterrno ();*/ // this is run from the signal thread, so don't set errno + goto out; + } + /* Get null-terminated path */ + if (!ReadFile (__fromthem, path, len, &nr, NULL) + || nr != len) + { + CloseHandle (hp); + /* __seterrno ();*/ // this is run from the signal thread, so don't set errno + goto out; + } + + fhandler_fifo *fh = cygheap->fdtab.find_fifo (path); + HANDLE it[2]; + if (fh == NULL) + it[0] = it[1] = NULL; + else + { + it[0] = fh->get_handle (); + it[1] = fh->get_output_handle (); + for (int i = 0; i < 2; i++) + if (!DuplicateHandle (hMainProc, it[i], hp, &it[i], 0, false, + DUPLICATE_SAME_ACCESS)) + { + it[0] = it[1] = NULL; /* FIXME: possibly left a handle open in child? */ + break; + } + } + + CloseHandle (hp); + if (!WriteFile (__tothem, it, sizeof (it), &nr, NULL)) + { + /*__seterrno ();*/ // this is run from the signal thread, so don't set errno + sigproc_printf ("WriteFile read handle failed, %E"); + } + + (void) ReadFile (__fromthem, &nr, sizeof (nr), &nr, NULL); + break; + } + } + +out: + if (__fromthem) + CloseHandle (__fromthem); + if (__tothem) + CloseHandle (__tothem); +} + +#define PIPEBUFSIZE (4096 * sizeof (DWORD)) + +commune_result +_pinfo::commune_send (DWORD code, ...) +{ + HANDLE fromthem = NULL, tome = NULL; + HANDLE fromme = NULL, tothem = NULL; + DWORD nr; + commune_result res; + va_list args; + + va_start (args, code); + + res.s = NULL; + res.n = 0; + + if (!this || !pid) + { + set_errno (ESRCH); + goto err; + } + if (!CreatePipe (&fromthem, &tome, &sec_all_nih, PIPEBUFSIZE)) + { + sigproc_printf ("first CreatePipe failed, %E"); + __seterrno (); + goto err; + } + if (!CreatePipe (&fromme, &tothem, &sec_all_nih, PIPEBUFSIZE)) + { + sigproc_printf ("second CreatePipe failed, %E"); + __seterrno (); + goto err; + } + EnterCriticalSection (&myself.lock); + myself->tothem = tome; + myself->fromthem = fromme; + myself->hello_pid = pid; + if (!WriteFile (tothem, &code, sizeof code, &nr, NULL) || nr != sizeof code) + { + __seterrno (); + goto err; + } + + if (sig_send (this, __SIGCOMMUNE)) + goto err; + + /* FIXME: Need something better than an busy loop here */ + bool isalive; + for (int i = 0; (isalive = alive ()) && (i < 10000); i++) + if (myself->hello_pid <= 0) + break; + else + low_priority_sleep (0); + + CloseHandle (tome); + tome = NULL; + CloseHandle (fromme); + fromme = NULL; + + if (!isalive) + { + set_errno (ESRCH); + goto err; + } + + if (myself->hello_pid < 0) + { + set_errno (ENOSYS); + goto err; + } + + size_t n; + switch (code) + { + case PICOM_CMDLINE: + if (!ReadFile (fromthem, &n, sizeof n, &nr, NULL) || nr != sizeof n) + { + __seterrno (); + goto err; + } + res.s = (char *) malloc (n); + char *p; + for (p = res.s; ReadFile (fromthem, p, n, &nr, NULL); p += nr) + continue; + if ((unsigned) (p - res.s) != n) + { + __seterrno (); + goto err; + } + res.n = n; + break; + case PICOM_FIFO: + { + char *path = va_arg (args, char *); + size_t len = strlen (path) + 1; + if (!WriteFile (tothem, &len, sizeof (len), &nr, NULL) + || nr != sizeof (len)) + { + __seterrno (); + goto err; + } + if (!WriteFile (tothem, path, len, &nr, NULL) || nr != len) + { + __seterrno (); + goto err; + } + + DWORD x = ReadFile (fromthem, res.handles, sizeof (res.handles), &nr, NULL); + (void) WriteFile (tothem, &x, sizeof (x), &x, NULL); + if (!x) + goto err; + + if (nr != sizeof (res.handles)) + { + set_errno (EPIPE); + goto err; + } + break; + } + } + CloseHandle (tothem); + CloseHandle (fromthem); + goto out; + +err: + if (tome) + CloseHandle (tome); + if (fromthem) + CloseHandle (fromthem); + if (tothem) + CloseHandle (tothem); + if (fromme) + CloseHandle (fromme); + memset (&res, 0, sizeof (res)); + +out: + myself->hello_pid = 0; + LeaveCriticalSection (&myself.lock); + return res; +} + +char * +_pinfo::cmdline (size_t& n) +{ + char *s; + if (!this || !pid) + return NULL; + if (pid != myself->pid) + { + commune_result cr = commune_send (PICOM_CMDLINE); + s = cr.s; + n = cr.n; + } + else + { + n = 1; + for (char **a = __argv; *a; a++) + n += strlen (*a) + 1; + char *p; + p = s = (char *) malloc (n); + for (char **a = __argv; *a; a++) + { + strcpy (p, *a); + p = strchr (p, '\0') + 1; + } + *p = '\0'; + } + return s; +} + +static DWORD WINAPI +proc_waiter (void *arg) +{ + pinfo vchild = *(pinfo *) arg; + vchild.preserve (); + + siginfo_t si; + si.si_signo = SIGCHLD; + si.si_code = SI_KERNEL; + si.si_pid = vchild->pid; + si.si_errno = 0; +#if 0 // FIXME: This is tricky to get right + si.si_utime = pchildren[rc]->rusage_self.ru_utime; + si.si_stime = pchildren[rc].rusage_self.ru_stime; +#else + si.si_utime = 0; + si.si_stime = 0; +#endif + pid_t pid = vchild->pid; + + for (;;) + { + DWORD nb; + char buf = '\0'; + if (!ReadFile (vchild.rd_proc_pipe, &buf, 1, &nb, NULL) + && GetLastError () != ERROR_BROKEN_PIPE) + { + system_printf ("error on read of child wait pipe %p, %E", vchild.rd_proc_pipe); + break; + } + + si.si_uid = vchild->uid; + + int proc_todo; + switch (buf) + { + case 0: + if (WIFEXITED (vchild->exitcode)) + si.si_sigval.sival_int = CLD_STOPPED; + else if (WCOREDUMP (vchild->exitcode)) + si.si_sigval.sival_int = CLD_DUMPED; + else + si.si_sigval.sival_int = CLD_KILLED; + CloseHandle (vchild.rd_proc_pipe); + vchild.rd_proc_pipe = NULL; + si.si_status = vchild->exitcode; + // proc_todo = PROC_CHILDTERMINATED; + vchild->process_state = PID_ZOMBIE; + break; + case SIGTTIN: + case SIGTTOU: + case SIGTSTP: + case SIGSTOP: + si.si_sigval.sival_int = CLD_STOPPED; + // proc_todo = PROC_CHILDSTOPPED; + break; + case SIGCONT: + // proc_todo = PROC_CHILDCONTINUED; + continue; + default: + system_printf ("unknown value %d on proc pipe", buf); + continue; + } + + /* Send a SIGCHLD to myself. We do this here, rather than in proc_subproc + to avoid the proc_subproc lock since the signal thread will eventually + be calling proc_subproc and could unnecessarily block. */ + sig_send (myself_nowait, si); + + /* If we're just stopped or got a continue signal, keep looping. + Otherwise, return this thread to the pool. */ + if (buf != '\0') + sigproc_printf ("looping"); + else + break; + } + sigproc_printf ("exiting wait thread for pid %d", pid); + return 0; +} + +int +pinfo::wait () +{ + HANDLE out; + /* FIXME: execed processes should be able to wait for pids that were started + by the process which execed them. */ + if (!CreatePipe (&rd_proc_pipe, &out, &sec_none_nih, 16)) + { + system_printf ("Couldn't create pipe tracker for pid %d, %E", + (*this)->pid); + return 0; + } + if (!DuplicateHandle (hMainProc, out, hProcess, &((*this)->wr_proc_pipe), 0, + TRUE, DUPLICATE_SAME_ACCESS)) + { + system_printf ("Couldn't duplicate pipe topid %d(%p), %E", (*this)->pid, + hProcess); + return 0; + } + CloseHandle (out); + + +#if 1 + DWORD tid; + HANDLE h = CreateThread (&sec_none_nih, 0, proc_waiter, this, 0, &tid); + if (!h) + sigproc_printf ("tracking thread creation failed for pid %d", (*this)->pid); + else + CloseHandle (h); +#else + cygthread *h = new cygthread (proc_waiter, this, "sig"); + if (!h) + sigproc_printf ("tracking thread creation failed for pid %d", (*this)->pid); + else + { + h->zap_h (); + sigproc_printf ("created tracking thread for pid %d, winpid %p, rd_pipe %p", + (*this)->pid, (*this)->dwProcessId, rd_proc_pipe); + } +#endif + return 1; +} + +void +pinfo::release () +{ + if (h) + { +#ifdef DEBUGGING + if (((DWORD) procinfo & 0x77000000) == 0x61000000) + try_to_debug (); +#endif + UnmapViewOfFile (procinfo); + procinfo = NULL; + ForceCloseHandle1 (h, pinfo_shared_handle); + h = NULL; + } +} + +/* DOCTOOL-START + + + cygwin_winpid_to_pid + + + extern "C" pid_t + cygwin_winpid_to_pid + + int winpid + + + Given a windows pid, converts to the corresponding Cygwin +pid, if any. Returns -1 if windows pid does not correspond to +a cygwin pid. + + Example use of cygwin_winpid_to_pid + + extern "C" cygwin_winpid_to_pid (int winpid); + pid_t mypid; + mypid = cygwin_winpid_to_pid (windows_pid); + + + + + DOCTOOL-END */ + +extern "C" pid_t +cygwin_winpid_to_pid (int winpid) +{ + pinfo p (cygwin_pid (winpid)); + if (p) + return p->pid; + + set_errno (ESRCH); + return (pid_t) -1; +} + +#include + +#define slop_pidlist 200 +#define size_pidlist(i) (sizeof (pidlist[0]) * ((i) + 1)) +#define size_pinfolist(i) (sizeof (pinfolist[0]) * ((i) + 1)) + +inline void +winpids::add (DWORD& nelem, bool winpid, DWORD pid) +{ + pid_t cygpid = cygwin_pid (pid); + if (nelem >= npidlist) + { + npidlist += slop_pidlist; + pidlist = (DWORD *) realloc (pidlist, size_pidlist (npidlist + 1)); + pinfolist = (pinfo *) realloc (pinfolist, size_pinfolist (npidlist + 1)); + } + + pinfolist[nelem].init (cygpid, PID_NOREDIR | (winpid ? PID_ALLPIDS : 0) + | pinfo_access); + if (winpid) + goto out; + + if (!pinfolist[nelem]) + { + if (!pinfo_access) + return; + pinfolist[nelem].init (cygpid, PID_NOREDIR | (winpid ? PID_ALLPIDS : 0)); + if (!pinfolist[nelem]) + return; + } + + /* Scan list of previously recorded pids to make sure that this pid hasn't + shown up before. This can happen when a process execs. */ + for (unsigned i = 0; i < nelem; i++) + if (pinfolist[i]->pid == pinfolist[nelem]->pid) + { + if ((_pinfo *) pinfolist[nelem] != (_pinfo *) myself) + pinfolist[nelem].release (); + return; + } + +out: + pidlist[nelem++] = pid; +} + +DWORD +winpids::enumNT (bool winpid) +{ + static DWORD szprocs; + static SYSTEM_PROCESSES *procs; + + DWORD nelem = 0; + if (!szprocs) + procs = (SYSTEM_PROCESSES *) malloc (sizeof (*procs) + (szprocs = 200 * sizeof (*procs))); + + NTSTATUS res; + for (;;) + { + res = NtQuerySystemInformation (SystemProcessesAndThreadsInformation, + procs, szprocs, NULL); + if (res == 0) + break; + + if (res == STATUS_INFO_LENGTH_MISMATCH) + procs = (SYSTEM_PROCESSES *) realloc (procs, szprocs += 200 * sizeof (*procs)); + else + { + system_printf ("error %p reading system process information", res); + return 0; + } + } + + SYSTEM_PROCESSES *px = procs; + for (;;) + { + if (px->ProcessId) + add (nelem, winpid, px->ProcessId); + if (!px->NextEntryDelta) + break; + px = (SYSTEM_PROCESSES *) ((char *) px + px->NextEntryDelta); + } + + return nelem; +} + +DWORD +winpids::enum9x (bool winpid) +{ + DWORD nelem = 0; + + HANDLE h = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); + if (!h) + { + system_printf ("Couldn't create process snapshot, %E"); + return 0; + } + + PROCESSENTRY32 proc; + proc.dwSize = sizeof (proc); + + if (Process32First (h, &proc)) + do + { + if (proc.th32ProcessID) + add (nelem, winpid, proc.th32ProcessID); + } + while (Process32Next (h, &proc)); + + CloseHandle (h); + return nelem; +} + +void +winpids::set (bool winpid) +{ + __malloc_lock (); + npids = (this->*enum_processes) (winpid); + if (pidlist) + pidlist[npids] = 0; + __malloc_unlock (); +} + +DWORD +winpids::enum_init (bool winpid) +{ + if (wincap.is_winnt ()) + enum_processes = &winpids::enumNT; + else + enum_processes = &winpids::enum9x; + + return (this->*enum_processes) (winpid); +} + +void +winpids::release () +{ + for (unsigned i = 0; i < npids; i++) + if (pinfolist[i] && (_pinfo *) pinfolist[i] != (_pinfo *) myself) + pinfolist[i].release (); +} + +winpids::~winpids () +{ + if (npidlist) + { + release (); + free (pidlist); + free (pinfolist); + } +} diff --git a/winsup/cygwin/pinfo.h b/winsup/cygwin/pinfo.h new file mode 100644 index 00000000000..89e4a6c9fe9 --- /dev/null +++ b/winsup/cygwin/pinfo.h @@ -0,0 +1,226 @@ +/* pinfo.h: process table info + + Copyright 2000, 2001, 2002, 2003 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef _PINFO_H +#define _PINFO_H +#include +#include "thread.h" + +struct commune_result +{ + char *s; + int n; + HANDLE handles[2]; +}; + +enum picom +{ + PICOM_CMDLINE = 1, + PICOM_FIFO = 2 +}; + +class _pinfo +{ +public: + /* Cygwin pid */ + pid_t pid; + + /* Various flags indicating the state of the process. See PID_ + constants below. */ + DWORD process_state; + + DWORD exitcode; /* set when process exits */ + +#define PINFO_REDIR_SIZE ((char *) &myself.procinfo->exitcode - (char *) myself.procinfo) + + /* Handle associated with initial Windows pid which started it all. */ + HANDLE pid_handle; + + /* True if started by a cygwin process (DWORD for hysterical reasons) */ + DWORD cygstarted; + + /* Parent process id. */ + pid_t ppid; + + /* dwProcessId contains the processid used for sending signals. It + * will be reset in a child process when it is capable of receiving + * signals. + */ + DWORD dwProcessId; + + /* Used to spawn a child for fork(), among other things. */ + char progname[CYG_MAX_PATH]; + + /* User information. + The information is derived from the GetUserName system call, + with the name looked up in /etc/passwd and assigned a default value + if not found. This data resides in the shared data area (allowing + tasks to store whatever they want here) so it's for informational + purposes only. */ + __uid32_t uid; /* User ID */ + __gid32_t gid; /* Group ID */ + pid_t pgid; /* Process group ID */ + pid_t sid; /* Session ID */ + int ctty; /* Control tty */ + bool has_pgid_children;/* True if we've forked or spawned children with our GID. */ + + /* Resources used by process. */ + long start_time; + struct rusage rusage_self; + struct rusage rusage_children; + + /* Non-zero if process was stopped by a signal. */ + char stopsig; + + /* commune */ + pid_t hello_pid; + HANDLE tothem; + HANDLE fromthem; + + void exit (UINT n, bool norecord = 0) __attribute__ ((noreturn, regparm(2))); + + inline void set_has_pgid_children () + { + if (pgid == pid) + has_pgid_children = 1; + } + + inline void set_has_pgid_children (bool val) {has_pgid_children = val;} + + inline sigset_t& getsigmask () + { + return sig_mask; + } + + inline void setsigmask (sigset_t mask) + { + sig_mask = mask; + } + + void commune_recv (); + commune_result commune_send (DWORD, ...); + bool alive (); + char *cmdline (size_t &); + void set_ctty (class tty_min *, int, class fhandler_tty_slave *); + + friend void __stdcall set_myself (HANDLE); + + /* signals */ + HANDLE sendsig; +private: + sigset_t sig_mask; +public: + HANDLE wr_proc_pipe; + friend class pinfo; +}; + +class pinfo +{ + HANDLE h; + _pinfo *procinfo; + bool destroy; +public: + HANDLE rd_proc_pipe; + HANDLE hProcess; + CRITICAL_SECTION lock; + void init (pid_t, DWORD, HANDLE = NULL) __attribute__ ((regparm(3))); + pinfo () {} + pinfo (_pinfo *x): procinfo (x) {} + pinfo (pid_t n) {init (n, 0);} + pinfo (pid_t n, DWORD flag) {init (n, flag);} + void release (); + int wait (); + ~pinfo () + { + if (destroy && procinfo) + release (); + } + + _pinfo *operator -> () const {return procinfo;} + int operator == (pinfo *x) const {return x->procinfo == procinfo;} + int operator == (pinfo &x) const {return x.procinfo == procinfo;} + int operator == (_pinfo *x) const {return x == procinfo;} + int operator == (void *x) const {return procinfo == x;} + int operator == (int x) const {return (int) procinfo == (int) x;} + int operator == (char *x) const {return (char *) procinfo == x;} + _pinfo *operator * () const {return procinfo;} + operator _pinfo * () const {return procinfo;} + // operator bool () const {return (int) h;} + void preserve() { destroy = false; } +#ifndef _SIGPROC_H + int remember () {system_printf ("remember is not here"); return 0;} +#else + int remember () + { + int res = proc_subproc (PROC_ADDCHILD, (DWORD) this); + destroy = res ? false : true; + return res; + } +#endif + HANDLE shared_handle () {return h;} + void set_acl(); +}; + +#define ISSTATE(p, f) (!!((p)->process_state & f)) +#define NOTSTATE(p, f) (!((p)->process_state & f)) + +class winpids +{ + DWORD *pidlist; + DWORD npidlist; + pinfo *pinfolist; + DWORD pinfo_access; // access type for pinfo open + DWORD (winpids::* enum_processes) (bool winpid); + DWORD enum_init (bool winpid); + DWORD enumNT (bool winpid); + DWORD enum9x (bool winpid); + void add (DWORD& nelem, bool, DWORD pid); +public: + DWORD npids; + inline void reset () { npids = 0; release (); } + void set (bool winpid); + winpids (int): pinfo_access (0), enum_processes (&winpids::enum_init) + { reset (); } + winpids (DWORD acc = 0): pidlist (NULL), npidlist (0), pinfolist (NULL), + enum_processes (&winpids::enum_init), npids (0) + { + pinfo_access = acc; + set (0); + } + inline DWORD& winpid (int i) const {return pidlist[i];} + inline _pinfo *operator [] (int i) const {return (_pinfo *) pinfolist[i];} + ~winpids (); + void release (); +}; + +extern __inline pid_t +cygwin_pid (pid_t pid) +{ + return (pid_t) (wincap.has_negative_pids ()) ? -(int) pid : pid; +} + +void __stdcall pinfo_init (char **, int); +void __stdcall set_myself (HANDLE h); +extern pinfo myself; + +#define _P_VFORK 0 +#define _P_SYSTEM 512 + +extern void __stdcall pinfo_fixup_after_fork (); +extern HANDLE hexec_proc; + +/* For mmaps across fork(). */ +int __stdcall fixup_mmaps_after_fork (HANDLE parent); +/* for shm areas across fork (). */ +int __stdcall fixup_shms_after_fork (); + +void __stdcall fill_rusage (struct rusage *, HANDLE); +void __stdcall add_rusage (struct rusage *, struct rusage *); +#endif /*_PINFO_H*/ diff --git a/winsup/cygwin/signal.cc b/winsup/cygwin/signal.cc new file mode 100644 index 00000000000..f57e0d09da0 --- /dev/null +++ b/winsup/cygwin/signal.cc @@ -0,0 +1,551 @@ +/* signal.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + + Written by Steve Chamberlain of Cygnus Support, sac@cygnus.com + Significant changes by Sergey Okhapkin + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include "cygerrno.h" +#include +#include "pinfo.h" +#include "sigproc.h" +#include "hires.h" +#include "security.h" +#include "cygtls.h" + +int sigcatchers; /* FIXME: Not thread safe. */ + +#define sigtrapped(func) ((func) != SIG_IGN && (func) != SIG_DFL) + +static inline void +set_sigcatchers (void (*oldsig) (int), void (*cursig) (int)) +{ +#ifdef DEBUGGING + int last_sigcatchers = sigcatchers; +#endif + if (!sigtrapped (oldsig) && sigtrapped (cursig)) + sigcatchers++; + else if (sigtrapped (oldsig) && !sigtrapped (cursig)) + sigcatchers--; +#ifdef DEBUGGING + if (last_sigcatchers != sigcatchers) + sigproc_printf ("last %d, old %d, cur %p, cur %p", last_sigcatchers, + sigcatchers, oldsig, cursig); +#endif +} + +extern "C" _sig_func_ptr +signal (int sig, _sig_func_ptr func) +{ + sig_dispatch_pending (); + _sig_func_ptr prev; + + /* check that sig is in right range */ + if (sig < 0 || sig >= NSIG || sig == SIGKILL || sig == SIGSTOP) + { + set_errno (EINVAL); + syscall_printf ("SIG_ERR = signal (%d, %p)", sig, func); + return (_sig_func_ptr) SIG_ERR; + } + + prev = global_sigs[sig].sa_handler; + global_sigs[sig].sa_handler = func; + global_sigs[sig].sa_mask = 0; + /* SA_RESTART is set to maintain BSD compatible signal behaviour by default. + This is also compatible with the behaviour of signal(2) in Linux. */ + global_sigs[sig].sa_flags |= SA_RESTART; + set_sigcatchers (prev, func); + + syscall_printf ("%p = signal (%d, %p)", prev, sig, func); + return prev; +} + +extern "C" int +nanosleep (const struct timespec *rqtp, struct timespec *rmtp) +{ + int res = 0; + sig_dispatch_pending (); + pthread_testcancel (); + + if ((unsigned int) rqtp->tv_sec > (HIRES_DELAY_MAX / 1000 - 1) + || (unsigned int) rqtp->tv_nsec > 999999999) + { + set_errno (EINVAL); + return -1; + } + DWORD resolution = gtod.resolution (); + DWORD req = ((rqtp->tv_sec * 1000 + (rqtp->tv_nsec + 999999) / 1000000 + + resolution - 1) / resolution) * resolution; + DWORD end_time = gtod.dmsecs () + req; + syscall_printf ("nanosleep (%ld)", req); + + int rc = pthread::cancelable_wait (signal_arrived, req); + DWORD rem; + if ((rem = end_time - gtod.dmsecs ()) > HIRES_DELAY_MAX) + rem = 0; + if (rc == WAIT_OBJECT_0) + { + (void) _my_tls.call_signal_handler (); + set_errno (EINTR); + res = -1; + } + + if (rmtp) + { + rmtp->tv_sec = rem / 1000; + rmtp->tv_nsec = (rem % 1000) * 1000000; + } + + syscall_printf ("%d = nanosleep (%ld, %ld)", res, req, rem); + return res; +} + +extern "C" unsigned int +sleep (unsigned int seconds) +{ + struct timespec req, rem; + req.tv_sec = seconds; + req.tv_nsec = 0; + nanosleep (&req, &rem); + return rem.tv_sec + (rem.tv_nsec > 0); +} + +extern "C" unsigned int +usleep (unsigned int useconds) +{ + struct timespec req; + req.tv_sec = useconds / 1000000; + req.tv_nsec = (useconds % 1000000) * 1000; + int res = nanosleep (&req, 0); + return res; +} + +extern "C" int +sigprocmask (int sig, const sigset_t *set, sigset_t *oldset) +{ + return handle_sigprocmask (sig, set, oldset, myself->getsigmask ()); +} + +int __stdcall +handle_sigprocmask (int sig, const sigset_t *set, sigset_t *oldset, sigset_t& opmask) +{ + sig_dispatch_pending (); + /* check that sig is in right range */ + if (sig < 0 || sig >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("signal %d out of range", sig); + return -1; + } + + if (oldset) + { + if (check_null_invalid_struct_errno (oldset)) + return -1; + *oldset = opmask; + } + + if (set) + { + if (check_invalid_read_struct_errno (set)) + return -1; + sigset_t newmask = opmask; + switch (sig) + { + case SIG_BLOCK: + /* add set to current mask */ + newmask |= *set; + break; + case SIG_UNBLOCK: + /* remove set from current mask */ + newmask &= ~*set; + break; + case SIG_SETMASK: + /* just set it */ + newmask = *set; + break; + default: + set_errno (EINVAL); + return -1; + } + (void) set_signal_mask (newmask, opmask); + } + return 0; +} + +static int +kill_worker (pid_t pid, siginfo_t& si) +{ + sig_dispatch_pending (); + + int res = 0; + pinfo dest (pid); + bool sendSIGCONT; + + if (!dest) + { + set_errno (ESRCH); + return -1; + } + + if ((sendSIGCONT = (si.si_signo < 0))) + si.si_signo = -si.si_signo; + + DWORD process_state = dest->process_state; + if (si.si_signo == 0) + { + res = proc_exists (dest) ? 0 : -1; + if (res < 0) + set_errno (ESRCH); + } + else if ((res = sig_send (dest, si))) + { + sigproc_printf ("%d = sig_send, %E ", res); + res = -1; + } + else if (sendSIGCONT) + { + siginfo_t si2; + si2.si_signo = SIGCONT; + si2.si_code = SI_KERNEL; + si2.si_pid = si2.si_uid = si2.si_errno = 0; + (void) sig_send (dest, si2); + } + + syscall_printf ("%d = kill_worker (%d, %d), process_state %p", res, pid, + si.si_signo, process_state); + return res; +} + +int +raise (int sig) +{ + return kill (myself->pid, sig); +} + +static int +kill0 (pid_t pid, siginfo_t& si) +{ + syscall_printf ("kill (%d, %d)", pid, si.si_signo); + /* check that sig is in right range */ + if (si.si_signo < 0 || si.si_signo >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("signal %d out of range", si.si_signo); + return -1; + } + + /* Silently ignore stop signals from a member of orphaned process group. + FIXME: Why??? */ + if (ISSTATE (myself, PID_ORPHANED) && + (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU)) + si.si_signo = 0; + + return (pid > 0) ? kill_worker (pid, si) : kill_pgrp (-pid, si); +} + +int +killsys (pid_t pid, int sig) +{ + siginfo_t si; + si.si_signo = sig; + si.si_code = SI_KERNEL; + si.si_pid = si.si_uid = si.si_errno = 0; + return kill0 (pid, si); +} +int +kill (pid_t pid, int sig) +{ + siginfo_t si; + si.si_signo = sig; + si.si_code = SI_USER; + si.si_pid = si.si_uid = si.si_errno = 0; + return kill0 (pid, si); +} + +int +kill_pgrp (pid_t pid, siginfo_t& si) +{ + int res = 0; + int found = 0; + int killself = 0; + + sigproc_printf ("pid %d, signal %d", pid, si.si_signo); + + winpids pids ((DWORD) PID_MAP_RW); + for (unsigned i = 0; i < pids.npids; i++) + { + _pinfo *p = pids[i]; + + if (!proc_exists (p)) + continue; + + /* Is it a process we want to kill? */ + if ((pid == 0 && (p->pgid != myself->pgid || p->ctty != myself->ctty)) || + (pid > 1 && p->pgid != pid) || + (si.si_signo < 0 && NOTSTATE (p, PID_STOPPED))) + continue; + sigproc_printf ("killing pid %d, pgrp %d, p->ctty %d, myself->ctty %d", + p->pid, p->pgid, p->ctty, myself->ctty); + if (p == myself) + killself++; + else if (kill_worker (p->pid, si)) + res = -1; + found++; + } + + if (killself && !exit_state && kill_worker (myself->pid, si)) + res = -1; + + if (!found) + { + set_errno (ESRCH); + res = -1; + } + syscall_printf ("%d = kill (%d, %d)", res, pid, si.si_signo); + return res; +} + +extern "C" int +killpg (pid_t pgrp, int sig) +{ + return kill (-pgrp, sig); +} + +extern "C" void +abort (void) +{ + sig_dispatch_pending (); + /* Flush all streams as per SUSv2. + From my reading of this document, this isn't strictly correct. + The streams are supposed to be flushed prior to exit. However, + if there is I/O in any signal handler that will not necessarily + be flushed. + However this is the way FreeBSD does it, and it is much easier to + do things this way, so... */ + if (_REENT->__cleanup) + _REENT->__cleanup (_REENT); + + /* Ensure that SIGABRT can be caught regardless of blockage. */ + sigset_t sig_mask; + sigfillset (&sig_mask); + sigdelset (&sig_mask, SIGABRT); + set_signal_mask (sig_mask); + + raise (SIGABRT); + (void) _my_tls.call_signal_handler (); /* Call any signal handler */ + do_exit (SIGABRT); /* signal handler didn't exit. Goodbye. */ +} + +extern "C" int +sigaction (int sig, const struct sigaction *newact, struct sigaction *oldact) +{ + sig_dispatch_pending (); + /* check that sig is in right range */ + if (sig < 0 || sig >= NSIG) + { + set_errno (EINVAL); + sigproc_printf ("signal %d, newact %p, oldact %p", sig, newact, oldact); + syscall_printf ("SIG_ERR = sigaction signal %d out of range", sig); + return -1; + } + + struct sigaction oa = global_sigs[sig]; + + if (newact) + sigproc_printf ("signal %d, newact %p (handler %p), oa %p", sig, newact, newact->sa_handler, oa, oa.sa_handler); + else + sigproc_printf ("signal %d, newact %p, oa %p", sig, newact, oa, oa.sa_handler); + + if (newact) + { + if (sig == SIGKILL || sig == SIGSTOP) + { + set_errno (EINVAL); + return -1; + } + global_sigs[sig] = *newact; + if (newact->sa_handler == SIG_IGN) + sig_clear (sig); + if (newact->sa_handler == SIG_DFL && sig == SIGCHLD) + sig_clear (sig); + set_sigcatchers (oa.sa_handler, newact->sa_handler); + if (sig == SIGCHLD) + { + myself->process_state &= ~PID_NOCLDSTOP; + if (newact->sa_flags & SA_NOCLDSTOP) + myself->process_state |= PID_NOCLDSTOP; + } + } + + if (oldact) + *oldact = oa; + + return 0; +} + +extern "C" int +sigaddset (sigset_t *set, const int sig) +{ + /* check that sig is in right range */ + if (sig <= 0 || sig >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("SIG_ERR = sigaddset signal %d out of range", sig); + return -1; + } + + *set |= SIGTOMASK (sig); + return 0; +} + +extern "C" int +sigdelset (sigset_t *set, const int sig) +{ + /* check that sig is in right range */ + if (sig <= 0 || sig >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("SIG_ERR = sigdelset signal %d out of range", sig); + return -1; + } + + *set &= ~SIGTOMASK (sig); + return 0; +} + +extern "C" int +sigismember (const sigset_t *set, int sig) +{ + /* check that sig is in right range */ + if (sig <= 0 || sig >= NSIG) + { + set_errno (EINVAL); + syscall_printf ("SIG_ERR = sigdelset signal %d out of range", sig); + return -1; + } + + if (*set & SIGTOMASK (sig)) + return 1; + else + return 0; +} + +extern "C" int +sigemptyset (sigset_t *set) +{ + *set = (sigset_t) 0; + return 0; +} + +extern "C" int +sigfillset (sigset_t *set) +{ + *set = ~((sigset_t) 0); + return 0; +} + +extern "C" int +sigsuspend (const sigset_t *set) +{ + return handle_sigsuspend (*set); +} + +extern "C" int +sigpause (int signal_mask) +{ + return handle_sigsuspend ((sigset_t) signal_mask); +} + +extern "C" int +pause (void) +{ + return handle_sigsuspend (myself->getsigmask ()); +} + +extern "C" int +siginterrupt (int sig, int flag) +{ + struct sigaction act; + (void) sigaction(sig, NULL, &act); + if (flag) + act.sa_flags &= ~SA_RESTART; + else + act.sa_flags |= SA_RESTART; + return sigaction (sig, &act, NULL); +} + +extern "C" int +sigwait (const sigset_t *set, int *sig_ptr) +{ + int sig = sigwaitinfo (set, NULL); + if (sig > 0) + *sig_ptr = sig; + return sig > 0 ? 0 : -1; +} + +extern "C" int +sigwaitinfo (const sigset_t *set, siginfo_t *info) +{ + pthread_testcancel (); + HANDLE h; + h = _my_tls.event = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + if (!h) + { + __seterrno (); + return -1; + } + + _my_tls.sigwait_mask = *set; + sig_dispatch_pending (true); + + int res; + switch (WaitForSingleObject (h, INFINITE)) + { + case WAIT_OBJECT_0: + if (!sigismember (set, _my_tls.infodata.si_signo)) + { + set_errno (EINTR); + res = -1; + } + else + { + if (info) + *info = _my_tls.infodata; + res = _my_tls.infodata.si_signo; + InterlockedExchange ((LONG *) &_my_tls.sig, (LONG) 0); + } + break; + default: + __seterrno (); + res = -1; + } + CloseHandle (h); + sigproc_printf ("returning sig %d", res); + return res; +} + +extern "C" int +sigqueue (pid_t pid, int sig, const union sigval value) +{ + siginfo_t si; + pinfo dest (pid); + if (!dest) + { + set_errno (ESRCH); + return -1; + } + si.si_signo = sig; + si.si_code = SI_USER; + si.si_pid = si.si_uid = si.si_errno = 0; + si.si_value = value; + return sig_send (dest, si); +} diff --git a/winsup/cygwin/sigproc.cc b/winsup/cygwin/sigproc.cc new file mode 100644 index 00000000000..1ddb74e4ef1 --- /dev/null +++ b/winsup/cygwin/sigproc.cc @@ -0,0 +1,1045 @@ +/* sigproc.cc: inter/intra signal and sub process handler + + Copyright 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + + Written by Christopher Faylor + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include +#include +#include +#include +#include "cygerrno.h" +#include "sync.h" +#include "pinfo.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "child_info_magic.h" +#include "shared_info.h" +#include "cygthread.h" +#include "cygtls.h" +#include "sigproc.h" +#include "perthread.h" +#include "exceptions.h" + +/* + * Convenience defines + */ +#define WSSC 60000 // Wait for signal completion +#define WPSP 40000 // Wait for proc_subproc mutex + +#define PSIZE 63 // Number of processes + +#define no_signals_available() (!hwait_sig || (myself->sendsig == INVALID_HANDLE_VALUE) || exit_state) + +#define NPROCS 256 + +/* + * Global variables + */ +struct sigaction *global_sigs; + +const char *__sp_fn ; +int __sp_ln; + +char NO_COPY myself_nowait_dummy[1] = {'0'};// Flag to sig_send that signal goes to + // current process but no wait is required +HANDLE NO_COPY signal_arrived; // Event signaled when a signal has + // resulted in a user-specified + // function call + +#define Static static NO_COPY + +HANDLE NO_COPY sigCONT; // Used to "STOP" a process +Static cygthread *hwait_sig; // Handle of wait_sig thread + +Static HANDLE wait_sig_inited; // Control synchronization of + // message queue startup + +Static int nprocs; // Number of deceased children +Static char cprocs[(NPROCS + 1) * sizeof (pinfo)]; // All my deceased children info +#define procs ((pinfo *) cprocs) +Static waitq waitq_head = {0, 0, 0, 0, 0, 0, 0};// Start of queue for wait'ing threads + +muto NO_COPY *sync_proc_subproc = NULL; // Control access to subproc stuff + +DWORD NO_COPY sigtid = 0; // ID of the signal thread + +/* Function declarations */ +static int __stdcall checkstate (waitq *) __attribute__ ((regparm (1))); +static __inline__ bool get_proc_lock (DWORD, DWORD); +static void __stdcall remove_proc (int); +static bool __stdcall stopped_or_terminated (waitq *, _pinfo *); +static DWORD WINAPI wait_sig (VOID *arg); + +/* wait_sig bookkeeping */ + +class pending_signals +{ + sigpacket sigs[NSIG + 1]; + sigpacket start; + sigpacket *end; + sigpacket *prev; + sigpacket *curr; +public: + void reset () {curr = &start; prev = &start;} + void add (sigpacket&); + void del (); + sigpacket *next (); + sigpacket *save () const {return curr;} + void restore (sigpacket *saved) {curr = saved;} + friend void __stdcall sig_dispatch_pending (bool); + friend DWORD WINAPI wait_sig (VOID *arg); +}; + +static pending_signals sigq; + +/* Functions */ +void __stdcall +sigalloc () +{ + cygheap->sigs = global_sigs = + (struct sigaction *) ccalloc (HEAP_SIGS, NSIG, sizeof (struct sigaction)); +} + +void __stdcall +signal_fixup_after_exec () +{ + global_sigs = cygheap->sigs; + /* Set up child's signal handlers */ + for (int i = 0; i < NSIG; i++) + { + global_sigs[i].sa_mask = 0; + if (global_sigs[i].sa_handler != SIG_IGN) + global_sigs[i].sa_handler = SIG_DFL; + } +} + +/* Determine if the parent process is alive. + */ + +bool __stdcall +my_parent_is_alive () +{ + bool res; + if (myself->cygstarted) + res = pid_exists (myself->ppid); + else + { + debug_printf ("Not started by cygwin app"); + res = false; + } + return res; +} + +void __stdcall +wait_for_sigthread () +{ + sigproc_printf ("wait_sig_inited %p", wait_sig_inited); + HANDLE hsig_inited = wait_sig_inited; + (void) WaitForSingleObject (hsig_inited, INFINITE); + wait_sig_inited = NULL; + (void) ForceCloseHandle1 (hsig_inited, wait_sig_inited); +} + +/* Get the sync_proc_subproc muto to control access to + * children, proc arrays. + * Attempt to handle case where process is exiting as we try to grab + * the mutex. + */ +static bool +get_proc_lock (DWORD what, DWORD val) +{ + Static int lastwhat = -1; + if (!sync_proc_subproc) + { + sigproc_printf ("sync_proc_subproc is NULL (1)"); + return false; + } + if (sync_proc_subproc->acquire (WPSP)) + { + lastwhat = what; + return true; + } + if (!sync_proc_subproc) + { + sigproc_printf ("sync_proc_subproc is NULL (2)"); + return false; + } + system_printf ("Couldn't aquire sync_proc_subproc for(%d,%d), last %d, %E", + what, val, lastwhat); + return true; +} + +static bool __stdcall +proc_can_be_signalled (_pinfo *p) +{ + if (p->sendsig == INVALID_HANDLE_VALUE) + { + set_errno (EPERM); + return false; + } + + if (p == myself_nowait || p == myself) + return hwait_sig; + + if (ISSTATE (p, PID_INITIALIZING) || + (((p)->process_state & (PID_ACTIVE | PID_IN_USE)) == + (PID_ACTIVE | PID_IN_USE))) + return true; + + set_errno (ESRCH); + return false; +} + +bool __stdcall +pid_exists (pid_t pid) +{ + pinfo p (pid); + return proc_exists (p); +} + +/* Test to determine if a process really exists and is processing signals. + */ +bool __stdcall +proc_exists (_pinfo *p) +{ + return p && !(p->process_state & (PID_EXITED | PID_ZOMBIE)); +} + +/* Return 1 if this is one of our children, zero otherwise. + FIXME: This really should be integrated with the rest of the proc_subproc + testing. Scanning these lists twice is inefficient. */ +bool __stdcall +mychild (int pid) +{ + pinfo p (pid); + return p && p->ppid == myself->pid; +} + +/* Handle all subprocess requests + */ +#define vchild (*((pinfo *) val)) +int __stdcall +proc_subproc (DWORD what, DWORD val) +{ + int rc = 1; + int potential_match; + _pinfo *child; + int clearing; + waitq *w; + +#define wval ((waitq *) val) + + sigproc_printf ("args: %x, %d", what, val); + + if (!get_proc_lock (what, val)) // Serialize access to this function + { + system_printf ("couldn't get proc lock. what %d, val %d", what, val); + goto out1; + } + + switch (what) + { + /* Add a new subprocess to the children arrays. + * (usually called from the main thread) + */ + case PROC_ADDCHILD: + /* Filled up process table? */ + if (nprocs >= NPROCS) + { + sigproc_printf ("proc table overflow: hit %d processes, pid %d\n", + nprocs, vchild->pid); + rc = 0; + set_errno (EMFILE); // FIXMENOW - what's the right errno? + break; + } + + vchild->ppid = myself->pid; + vchild->uid = myself->uid; + vchild->gid = myself->gid; + vchild->pgid = myself->pgid; + vchild->sid = myself->sid; + vchild->ctty = myself->ctty; + vchild->cygstarted = true; + vchild->process_state |= PID_INITIALIZING | (myself->process_state & PID_USETTY); + procs[nprocs] = vchild; + rc = procs[nprocs].wait (); + if (rc) + { + sigproc_printf ("added pid %d to proc table, slot %d", vchild->pid, + nprocs); + nprocs++; + } + break; + + /* Handle a wait4() operation. Allocates an event for the calling + * thread which is signaled when the appropriate pid exits or stops. + * (usually called from the main thread) + */ + case PROC_WAIT: + wval->ev = NULL; // Don't know event flag yet + + if (wval->pid <= 0) + child = NULL; // Not looking for a specific pid + else if (!mychild (wval->pid)) + goto out; // invalid pid. flag no such child + + wval->status = 0; // Don't know status yet + sigproc_printf ("wval->pid %d, wval->options %d", wval->pid, wval->options); + + /* If the first time for this thread, create a new event, otherwise + * reset the event. + */ + if ((wval->ev = wval->thread_ev) == NULL) + { + wval->ev = wval->thread_ev = CreateEvent (&sec_none_nih, TRUE, + FALSE, NULL); + ProtectHandle1 (wval->ev, wq_ev); + } + + ResetEvent (wval->ev); + w = waitq_head.next; + waitq_head.next = wval; /* Add at the beginning. */ + wval->next = w; /* Link in rest of the list. */ + clearing = 0; + goto scan_wait; + + /* Clear all waiting threads. Called from exceptions.cc prior to + the main thread's dispatch to a signal handler function. + (called from wait_sig thread) */ + case PROC_CLEARWAIT: + /* Clear all "wait"ing threads. */ + if (val) + sigproc_printf ("clear waiting threads"); + else + sigproc_printf ("looking for processes to reap"); + clearing = val; + + scan_wait: + /* Scan the linked list of wait()ing threads. If a wait's parameters + match this pid, then activate it. */ + for (w = &waitq_head; w->next != NULL; w = w->next) + { + if ((potential_match = checkstate (w)) > 0) + sigproc_printf ("released waiting thread"); + else if (!clearing && !(w->next->options & WNOHANG) && potential_match < 0) + sigproc_printf ("only found non-terminated children"); + else if (potential_match <= 0) // nothing matched + { + sigproc_printf ("waiting thread found no children"); + HANDLE oldw = w->next->ev; + w->next->pid = 0; + if (clearing) + w->next->status = -1; /* flag that a signal was received */ + else if (!potential_match || !(w->next->options & WNOHANG)) + w->next->ev = NULL; + if (!SetEvent (oldw)) + system_printf ("couldn't wake up wait event %p, %E", oldw); + w->next = w->next->next; + } + if (w->next == NULL) + break; + } + + if (!clearing) + sigproc_printf ("finished processing terminated/stopped child"); + else + { + waitq_head.next = NULL; + sigproc_printf ("finished clearing"); + } + + // FIXMENOW: What is supposed to happen here? + if (global_sigs[SIGCHLD].sa_handler == (void *) SIG_IGN) + while (nprocs) + remove_proc (0); + break; + } + +out: + sync_proc_subproc->release (); // Release the lock +out1: + sigproc_printf ("returning %d", rc); + return rc; +} + +// FIXME: This is inelegant +void +_cygtls::remove_wq (DWORD wait) +{ + if (sync_proc_subproc && sync_proc_subproc->acquire (wait)) + { + for (waitq *w = &waitq_head; w->next != NULL; w = w->next) + if (w->next == &wq) + { + ForceCloseHandle1 (wq.thread_ev, wq_ev); + w->next = wq.next; + break; + } + sync_proc_subproc->release (); + } +} + +/* Terminate the wait_subproc thread. + * Called on process exit. + * Also called by spawn_guts to disassociate any subprocesses from this + * process. Subprocesses will then know to clean up after themselves and + * will not become procs. + */ +void __stdcall +proc_terminate (void) +{ + sigproc_printf ("nprocs %d", nprocs); + /* Signal processing is assumed to be blocked in this routine. */ + if (nprocs) + { + sync_proc_subproc->acquire (WPSP); + + (void) proc_subproc (PROC_CLEARWAIT, 1); + + /* Clean out proc processes from the pid list. */ + int i; + for (i = 0; i < nprocs; i++) + { + procs[i]->ppid = 1; + if (!proc_exists (procs[i])) + procs[i]->process_state = PID_EXITED; /* CGF FIXME - still needed? */ + procs[i].release (); + } + nprocs = 0; + sync_proc_subproc->release (); + } + sigproc_printf ("leaving"); +} + +/* Clear pending signal */ +void __stdcall +sig_clear (int target_sig) +{ + if (GetCurrentThreadId () != sigtid) + sig_send (myself, -target_sig); + else + { + sigpacket *q; + sigpacket *save = sigq.save (); + sigq.reset (); + while ((q = sigq.next ())) + if (q->si.si_signo == target_sig) + { + q->si.si_signo = __SIGDELETE; + break; + } + sigq.restore (save); + } + return; +} + +extern "C" int +sigpending (sigset_t *mask) +{ + sigset_t outset = (sigset_t) sig_send (myself, __SIGPENDING); + if (outset == SIG_BAD_MASK) + return -1; + *mask = outset; + return 0; +} + +/* Force the wait_sig thread to wake up and scan for pending signals */ +void __stdcall +sig_dispatch_pending (bool fast) +{ + if (exit_state || GetCurrentThreadId () == sigtid || !sigq.start.next) + { +#ifdef DEBUGGING + sigproc_printf ("exit_state %d, cur thread id %p, sigtid %p, sigq.start.next %p", + exit_state, GetCurrentThreadId (), sigtid, sigq.start.next); +#endif + return; + } + +#ifdef DEBUGGING + sigproc_printf ("flushing"); +#endif + (void) sig_send (myself, fast ? __SIGFLUSHFAST : __SIGFLUSH); +} + +/* Message initialization. Called from dll_crt0_1 + * + * This routine starts the signal handling thread. The wait_sig_inited + * event is used to signal that the thread is ready to handle signals. + * We don't wait for this during initialization but instead detect it + * in sig_send to gain a little concurrency. + */ +void __stdcall +sigproc_init () +{ + wait_sig_inited = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); + ProtectHandle (wait_sig_inited); + + /* sync_proc_subproc is used by proc_subproc. It serialises + * access to the children and proc arrays. + */ + new_muto (sync_proc_subproc); + + /* local event signaled when main thread has been dispatched + to a signal handler function. */ + signal_arrived = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); + ProtectHandle (signal_arrived); + + hwait_sig = new cygthread (wait_sig, cygself, "sig"); + hwait_sig->zap_h (); + + global_sigs[SIGSTOP].sa_flags = SA_RESTART | SA_NODEFER; + sigproc_printf ("process/signal handling enabled(%x)", myself->process_state); + return; +} + +/* Called on process termination to terminate signal and process threads. + */ +void __stdcall +sigproc_terminate (void) +{ + extern HANDLE hExeced; + hwait_sig = NULL; + + if (myself->sendsig == INVALID_HANDLE_VALUE) + sigproc_printf ("sigproc handling not active"); + else + { + sigproc_printf ("entering"); + // finished with anything it is doing + if (!hExeced) + { + HANDLE sendsig = myself->sendsig; + myself->sendsig = INVALID_HANDLE_VALUE; + CloseHandle (sendsig); + } + } + proc_terminate (); // Terminate process handling thread + + return; +} + +int __stdcall +sig_send (_pinfo *p, int sig) +{ + siginfo_t si; + si.si_signo = sig; + si.si_code = SI_KERNEL; + si.si_pid = si.si_uid = si.si_errno = 0; + return sig_send (p, si); +} + +/* Send a signal to another process by raising its signal semaphore. + If pinfo *p == NULL, send to the current process. + If sending to this process, wait for notification that a signal has + completed before returning. */ +int __stdcall +sig_send (_pinfo *p, siginfo_t& si, _cygtls *tls) +{ + int rc = 1; + bool its_me; + HANDLE sendsig; + sigpacket pack; + + pack.wakeup = NULL; + bool wait_for_completion; + if (!(its_me = (p == NULL || p == myself || p == myself_nowait))) + wait_for_completion = false; + else + { + if (no_signals_available ()) + { + sigproc_printf ("hwait_sig %p, myself->sendsig %p, exit_state %d", + hwait_sig, myself->sendsig, exit_state); + goto out; // Either exiting or not yet initializing + } + if (wait_sig_inited) + wait_for_sigthread (); + wait_for_completion = p != myself_nowait && _my_tls.isinitialized (); + p = myself; + } + + /* It is possible that the process is not yet ready to receive messages + * or that it has exited. Detect this. + */ + if (!proc_can_be_signalled (p)) /* Is the process accepting messages? */ + { + sigproc_printf ("invalid pid %d(%x), signal %d", + p->pid, p->process_state, si.si_signo); + goto out; + } + + if (its_me) + sendsig = myself->sendsig; + else + { + for (int i = 0; !p->dwProcessId && i < 10000; i++) + low_priority_sleep (0); + HANDLE hp = OpenProcess (PROCESS_DUP_HANDLE, false, p->dwProcessId); + if (!hp) + { + sigproc_printf ("OpenProcess failed, %E"); + __seterrno (); + goto out; + } + VerifyHandle (hp); + for (int i = 0; !p->sendsig && i < 10000; i++) + low_priority_sleep (0); + if (!DuplicateHandle (hp, p->sendsig, hMainProc, &sendsig, false, 0, + DUPLICATE_SAME_ACCESS) || !sendsig) + { + CloseHandle (hp); + sigproc_printf ("DuplicateHandle failed, %E"); + __seterrno (); + goto out; + } + CloseHandle (hp); + VerifyHandle (sendsig); + } + + sigproc_printf ("sendsig %p, pid %d, signal %d, its_me %d", sendsig, p->pid, si.si_signo, its_me); + + sigset_t pending; + if (!its_me) + pack.mask = NULL; + else if (si.si_signo == __SIGPENDING) + pack.mask = &pending; + else if (si.si_signo == __SIGFLUSH || si.si_signo > 0) + pack.mask = &myself->getsigmask (); + else + pack.mask = NULL; + + pack.si = si; + if (!pack.si.si_pid) + pack.si.si_pid = myself->pid; + if (!pack.si.si_uid) + pack.si.si_uid = myself->uid; + pack.pid = myself->pid; + pack.tls = (_cygtls *) tls; + if (wait_for_completion) + { + pack.wakeup = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + sigproc_printf ("wakeup %p", pack.wakeup); + ProtectHandle (pack.wakeup); + } + + DWORD nb; + if (!WriteFile (sendsig, &pack, sizeof (pack), &nb, NULL) || nb != sizeof (pack)) + { + /* Couldn't send to the pipe. This probably means that the + process is exiting. */ + if (!its_me) + { + sigproc_printf ("WriteFile for pipe %p failed, %E", sendsig); + __seterrno (); + ForceCloseHandle (sendsig); + } + else + { + if (no_signals_available ()) + sigproc_printf ("I'm going away now"); + else + system_printf ("error sending signal %d to pid %d, pipe handle %p, %E", + si.si_signo, p->pid, sendsig); + } + goto out; + } + + + /* No need to wait for signal completion unless this was a signal to + this process. + + If it was a signal to this process, wait for a dispatched signal. + Otherwise just wait for the wait_sig to signal that it has finished + processing the signal. */ + if (wait_for_completion) + { + sigproc_printf ("Waiting for pack.wakeup %p", pack.wakeup); + rc = WaitForSingleObject (pack.wakeup, WSSC); + } + else + { + rc = WAIT_OBJECT_0; + sigproc_printf ("Not waiting for sigcomplete. its_me %d signal %d", + its_me, si.si_signo); + if (!its_me) + ForceCloseHandle (sendsig); + } + + if (pack.wakeup) + { + ForceCloseHandle (pack.wakeup); + pack.wakeup = NULL; + } + + if (rc == WAIT_OBJECT_0) + rc = 0; // Successful exit + else + { + if (!no_signals_available ()) + system_printf ("wait for sig_complete event failed, signal %d, rc %d, %E", + si.si_signo, rc); + set_errno (ENOSYS); + rc = -1; + } + + if (wait_for_completion && si.si_signo != __SIGFLUSHFAST) + _my_tls.call_signal_handler (); + +out: + if (pack.wakeup) + ForceCloseHandle (pack.wakeup); + if (si.si_signo != __SIGPENDING) + /* nothing */; + else if (!rc) + rc = (int) pending; + else + rc = SIG_BAD_MASK; + sigproc_printf ("returning %p from sending signal %d", rc, si.si_signo); + return rc; +} + +/* Initialize some of the memory block passed to child processes + by fork/spawn/exec. */ + +void __stdcall +init_child_info (DWORD chtype, child_info *ch, HANDLE subproc_ready) +{ + memset (ch, 0, sizeof *ch); + ch->cb = chtype == PROC_FORK ? sizeof (child_info_fork) : sizeof (child_info); + ch->intro = PROC_MAGIC_GENERIC; + ch->magic = CHILD_INFO_MAGIC; + ch->type = chtype; + ch->subproc_ready = subproc_ready; + ch->fhandler_union_cb = sizeof (fhandler_union); + ch->user_h = cygwin_user_h; +} + +/* Check the state of all of our children to see if any are stopped or + * terminated. + */ +static int __stdcall +checkstate (waitq *parent_w) +{ + int potential_match = 0; + + sigproc_printf ("nprocs %d", nprocs); + + /* Check already dead processes first to see if they match the criteria + * given in w->next. */ + int res; + for (int i = 0; i < nprocs; i++) + if ((res = stopped_or_terminated (parent_w, procs[i]))) + { + remove_proc (i); + potential_match = 1; + goto out; + } + + potential_match = -!!nprocs; + +out: + sigproc_printf ("returning %d", potential_match); + return potential_match; +} + +/* Remove a proc from procs by swapping it with the last child in the list. + Also releases shared memory of exited processes. */ +static void __stdcall +remove_proc (int ci) +{ + if (!proc_exists (procs[ci])) + { + sigproc_printf ("removing procs[%d], pid %d, nprocs %d", ci, procs[ci]->pid, + nprocs); + procs[ci].release (); + if (ci < --nprocs) + procs[ci] = procs[nprocs]; + } +} + +/* Check status of child process vs. waitq member. + + parent_w is the pointer to the parent of the waitq member in question. + child is the subprocess being considered. + + Returns non-zero if waiting thread released. */ +static bool __stdcall +stopped_or_terminated (waitq *parent_w, _pinfo *child) +{ + int might_match; + waitq *w = parent_w->next; + + sigproc_printf ("considering pid %d", child->pid); + if (w->pid == -1) + might_match = 1; + else if (w->pid == 0) + might_match = child->pgid == myself->pgid; + else if (w->pid < 0) + might_match = child->pgid == -w->pid; + else + might_match = (w->pid == child->pid); + + if (!might_match) + return 0; + + int terminated; + + if (!((terminated = child->process_state == PID_ZOMBIE) || + ((w->options & WUNTRACED) && child->stopsig))) + return 0; + + parent_w->next = w->next; /* successful wait. remove from wait queue */ + w->pid = child->pid; + + if (!terminated) + { + sigproc_printf ("stopped child"); + w->status = (child->stopsig << 8) | 0x7f; + child->stopsig = 0; + } + else /* Should only get here when child has been moved to the procs array */ + { + w->status = child->exitcode; + + add_rusage (&myself->rusage_children, &child->rusage_children); + add_rusage (&myself->rusage_children, &child->rusage_self); + + if (w->rusage) + { + add_rusage ((struct rusage *) w->rusage, &child->rusage_children); + add_rusage ((struct rusage *) w->rusage, &child->rusage_self); + } + } + + if (!SetEvent (w->ev)) /* wake up wait4 () immediately */ + system_printf ("couldn't wake up wait event %p, %E", w->ev); + return true; +} + +static void +talktome () +{ + winpids pids ((DWORD) PID_MAP_RW); + for (unsigned i = 0; i < pids.npids; i++) + if (pids[i]->hello_pid == myself->pid) + if (!IsBadWritePtr (pids[i], sizeof (_pinfo))) + pids[i]->commune_recv (); +} + +void +pending_signals::add (sigpacket& pack) +{ + sigpacket *se; + if (sigs[pack.si.si_signo].si.si_signo) + return; + se = sigs + pack.si.si_signo; + *se = pack; + se->mask = &myself->getsigmask (); + se->next = NULL; + if (end) + end->next = se; + end = se; + if (!start.next) + start.next = se; +} + +void +pending_signals::del () +{ + sigpacket *next = curr->next; + prev->next = next; + curr->si.si_signo = 0; +#ifdef DEBUGGING + curr->next = NULL; +#endif + if (end == curr) + end = prev; + curr = next; +} + +sigpacket * +pending_signals::next () +{ + sigpacket *res; + prev = curr; + if (!curr || !(curr = curr->next)) + res = NULL; + else + res = curr; + return res; +} + +/* Process signals by waiting for signal data to arrive in a pipe. + Set a completion event if one was specified. */ +static DWORD WINAPI +wait_sig (VOID *self) +{ + HANDLE readsig; + char sa_buf[1024]; + Static bool holding_signals; + + /* Initialization */ + (void) SetThreadPriority (GetCurrentThread (), WAIT_SIG_PRIORITY); + + if (!CreatePipe (&readsig, &myself->sendsig, sec_user_nih (sa_buf), 0)) + api_fatal ("couldn't create signal pipe, %E"); + sigCONT = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + + /* Setting dwProcessId flags that this process is now capable of receiving + signals. Prior to this, dwProcessId was set to the windows pid of + of the original windows process which spawned us unless this was a + "toplevel" process. */ + myself->dwProcessId = GetCurrentProcessId (); + myself->process_state |= PID_ACTIVE; + myself->process_state &= ~PID_INITIALIZING; + + sigproc_printf ("myself->dwProcessId %u", myself->dwProcessId); +#if 0 + /* If we've been execed, then there is still a stub left in the previous + windows process waiting to see if it's started a cygwin process or not. + Signalling subproc_ready indicates that we are a cygwin process. */ + if (child_proc_info && child_proc_info->type == PROC_EXEC) + { + debug_printf ("subproc_ready %p", child_proc_info->subproc_ready); + if (!SetEvent (child_proc_info->subproc_ready)) + system_printf ("SetEvent (subproc_ready) failed, %E"); + ForceCloseHandle1 (child_proc_info->subproc_ready, subproc_ready); + /* Initialize an "indirect" pid block so that if someone looks up this + process via its Windows PID it will be redirected to the appropriate + Cygwin PID shared memory block. */ + static pinfo NO_COPY myself_identity; + myself_identity.init (cygwin_pid (myself->dwProcessId), PID_EXECED); + } +#endif + + SetEvent (wait_sig_inited); + sigtid = GetCurrentThreadId (); + + exception_list el; + _my_tls.init_threadlist_exceptions (&el); + debug_printf ("entering ReadFile loop, readsig %p, myself->sendsig %p", + readsig, myself->sendsig); + + for (;;) + { + DWORD nb; + sigpacket pack; + if (!ReadFile (readsig, &pack, sizeof (pack), &nb, NULL)) + break; + if (myself->sendsig == INVALID_HANDLE_VALUE) + break; + + if (nb != sizeof (pack)) + { + system_printf ("short read from signal pipe: %d != %d", nb, + sizeof (pack)); + continue; + } + + if (!pack.si.si_signo) + { +#ifdef DEBUGGING + system_printf ("zero signal?"); +#endif + continue; + } + + sigset_t dummy_mask; + if (!pack.mask) + { + dummy_mask = myself->getsigmask (); + pack.mask = &dummy_mask; + } + + sigpacket *q; + bool clearwait = false; + switch (pack.si.si_signo) + { + case __SIGCOMMUNE: + talktome (); + break; + case __SIGSTRACE: + strace.hello (); + break; + case __SIGPENDING: + *pack.mask = 0; + unsigned bit; + sigq.reset (); + while ((q = sigq.next ())) + if (myself->getsigmask () & (bit = SIGTOMASK (q->si.si_signo))) + *pack.mask |= bit; + break; + case __SIGHOLD: + holding_signals = 1; + break; + case __SIGNOHOLD: + holding_signals = 0; + /* fall through, intentionally */ + case __SIGFLUSH: + case __SIGFLUSHFAST: + sigq.reset (); + while ((q = sigq.next ())) + { + int sig = q->si.si_signo; + if (sig == __SIGDELETE || q->process () > 0) + sigq.del (); + if (sig == __SIGNOHOLD && q->si.si_signo == SIGCHLD) + clearwait = true; + } + break; + default: + if (pack.si.si_signo < 0) + sig_clear (-pack.si.si_signo); + else if (holding_signals) + sigq.add (pack); + else + { + int sig = pack.si.si_signo; + // FIXME: Not quite right when taking threads into consideration. + // Do we need a per-thread queue? + if (sigq.sigs[sig].si.si_signo) + sigproc_printf ("sig %d already queued", pack.si.si_signo); + else + { + int sigres = pack.process (); + if (sigres <= 0) + { +#ifdef DEBUGGING2 + if (!sigres) + system_printf ("Failed to arm signal %d from pid %d", pack.sig, pack.pid); +#endif + sigq.add (pack); // FIXME: Shouldn't add this in !sh condition + } + } + if (sig == SIGCHLD) + clearwait = true; + } + break; + } + if (clearwait) + proc_subproc (PROC_CLEARWAIT, 0); + if (pack.wakeup) + { + SetEvent (pack.wakeup); + sigproc_printf ("signalled %p", pack.wakeup); + } + } + + sigproc_printf ("done"); + ExitThread (0); +} diff --git a/winsup/cygwin/sigproc.h b/winsup/cygwin/sigproc.h new file mode 100644 index 00000000000..8f648b1d512 --- /dev/null +++ b/winsup/cygwin/sigproc.h @@ -0,0 +1,97 @@ +/* sigproc.h + + Copyright 1997, 1998, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef _SIGPROC_H +#define _SIGPROC_H +#include + +#ifdef NSIG +enum +{ + __SIGFLUSH = -(NSIG + 1), + __SIGSTRACE = -(NSIG + 2), + __SIGCOMMUNE = -(NSIG + 3), + __SIGPENDING = -(NSIG + 4), + __SIGDELETE = -(NSIG + 5), + __SIGFLUSHFAST = -(NSIG + 6), + __SIGHOLD = -(NSIG + 7), + __SIGNOHOLD = -(NSIG + 8) +}; +#endif + +#define SIG_BAD_MASK (1 << (SIGKILL - 1)) + +enum procstuff +{ + PROC_ADDCHILD = 1, // add a new subprocess to list + PROC_CHILDTERMINATED = 2, // a child died + PROC_CLEARWAIT = 3, // clear all waits - signal arrived + PROC_WAIT = 4, // setup for wait() for subproc + PROC_CHILDSTOPPED = 5, // register process as "stopped" + PROC_CHILDCONTINUED = 6, // remove process from "stopped" list + PROC_NOTHING = 7 // nothing, really +}; + +struct sigpacket +{ + siginfo_t si; + pid_t pid; + class _cygtls *tls; + sigset_t *mask; + union + { + HANDLE wakeup; + struct sigpacket *next; + }; + int __stdcall process () __attribute__ ((regparm (1))); +}; + +extern HANDLE signal_arrived; +extern HANDLE sigCONT; + +bool __stdcall my_parent_is_alive (); +void __stdcall sig_dispatch_pending (bool fast = false); +#ifdef _PINFO_H +extern "C" void __stdcall set_signal_mask (sigset_t newmask, sigset_t = myself->getsigmask ()); +#endif +int __stdcall handle_sigprocmask (int sig, const sigset_t *set, + sigset_t *oldset, sigset_t& opmask) + __attribute__ ((regparm (3))); + +extern "C" void __stdcall reset_signal_arrived (); +void __stdcall sig_clear (int) __attribute__ ((regparm (1))); +void __stdcall sig_set_pending (int) __attribute__ ((regparm (1))); +int __stdcall handle_sigsuspend (sigset_t); + +int __stdcall proc_subproc (DWORD, DWORD) __attribute__ ((regparm (2))); + +class _pinfo; +void __stdcall proc_terminate (); +void __stdcall sigproc_init (); +void __stdcall sigproc_terminate (); +bool __stdcall proc_exists (_pinfo *) __attribute__ ((regparm(1))); +bool __stdcall pid_exists (pid_t) __attribute__ ((regparm(1))); +int __stdcall sig_send (_pinfo *, siginfo_t&, class _cygtls *tls = NULL) __attribute__ ((regparm (3))); +int __stdcall sig_send (_pinfo *, int) __attribute__ ((regparm (2))); +void __stdcall signal_fixup_after_exec (); +void __stdcall wait_for_sigthread (); +void __stdcall sigalloc (); + +int kill_pgrp (pid_t, siginfo_t&); +int killsys (pid_t, int); + +extern char myself_nowait_dummy[]; + +extern struct sigaction *global_sigs; + +#define WAIT_SIG_PRIORITY THREAD_PRIORITY_TIME_CRITICAL + +#define myself_nowait ((_pinfo *)myself_nowait_dummy) +#endif /*_SIGPROC_H*/ diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc new file mode 100644 index 00000000000..43bd1481f18 --- /dev/null +++ b/winsup/cygwin/spawn.cc @@ -0,0 +1,1088 @@ +/* spawn.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cygerrno.h" +#include +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "sigproc.h" +#include "cygheap.h" +#include "child_info.h" +#include "shared_info.h" +#include "pinfo.h" +#define NEED_VFORK +#include "perthread.h" +#include "registry.h" +#include "environ.h" +#include "cygthread.h" + +#define LINE_BUF_CHUNK (CYG_MAX_PATH * 2) + +static suffix_info std_suffixes[] = +{ + suffix_info (".exe", 1), suffix_info ("", 1), + suffix_info (".com"), suffix_info (".cmd"), + suffix_info (".bat"), suffix_info (".dll"), + suffix_info (NULL) +}; + +HANDLE hExeced; +DWORD dwExeced; + +/* Add .exe to PROG if not already present and see if that exists. + If not, return PROG (converted from posix to win32 rules if necessary). + The result is always BUF. + + Returns (possibly NULL) suffix */ + +static const char * +perhaps_suffix (const char *prog, path_conv& buf) +{ + char *ext; + + debug_printf ("prog '%s'", prog); + buf.check (prog, PC_SYM_FOLLOW | PC_FULL, std_suffixes); + + if (!buf.exists () || buf.isdir ()) + ext = NULL; + else if (buf.known_suffix) + ext = (char *) buf + (buf.known_suffix - buf.get_win32 ()); + else + ext = strchr (buf, '\0'); + + debug_printf ("buf %s, suffix found '%s'", (char *) buf, ext); + return ext; +} + +/* Find an executable name, possibly by appending known executable + suffixes to it. The win32-translated name is placed in 'buf'. + Any found suffix is returned in known_suffix. + + If the file is not found and !null_if_not_found then the win32 version + of name is placed in buf and returned. Otherwise the contents of buf + is undefined and NULL is returned. */ + +const char * __stdcall +find_exec (const char *name, path_conv& buf, const char *mywinenv, + unsigned opt, const char **known_suffix) +{ + const char *suffix = ""; + debug_printf ("find_exec (%s)", name); + const char *retval = buf; + char tmp[CYG_MAX_PATH]; + const char *posix = (opt & FE_NATIVE) ? NULL : name; + bool has_slash = strchr (name, '/'); + + /* Check to see if file can be opened as is first. + Win32 systems always check . first, but PATH may not be set up to + do this. */ + if ((has_slash || opt & FE_CWD) + && (suffix = perhaps_suffix (name, buf)) != NULL) + { + if (posix && !has_slash) + { + tmp[0] = '.'; + tmp[1] = '/'; + strcpy (tmp + 2, name); + posix = tmp; + } + goto out; + } + + win_env *winpath; + const char *path; + const char *posix_path; + + /* Return the error condition if this is an absolute path or if there + is no PATH to search. */ + if (has_slash || strchr (name, '\\') || isdrive (name) + || !(winpath = getwinenv (mywinenv)) + || !(path = winpath->get_native ()) || *path == '\0') + goto errout; + + debug_printf ("%s%s", mywinenv, path); + + posix = (opt & FE_NATIVE) ? NULL : tmp; + posix_path = winpath->get_posix () - 1; + /* Iterate over the specified path, looking for the file with and + without executable extensions. */ + do + { + posix_path++; + char *eotmp = strccpy (tmp, &path, ';'); + /* An empty path or '.' means the current directory, but we've + already tried that. */ + if (opt & FE_CWD && (tmp[0] == '\0' || (tmp[0] == '.' && tmp[1] == '\0'))) + continue; + + *eotmp++ = '\\'; + strcpy (eotmp, name); + + debug_printf ("trying %s", tmp); + + if ((suffix = perhaps_suffix (tmp, buf)) != NULL) + { + if (posix == tmp) + { + eotmp = strccpy (tmp, &posix_path, ':'); + if (eotmp == tmp) + *eotmp++ = '.'; + *eotmp++ = '/'; + strcpy (eotmp, name); + } + goto out; + } + } + while (*path && *++path && (posix_path = strchr (posix_path, ':'))); + + errout: + posix = NULL; + /* Couldn't find anything in the given path. + Take the appropriate action based on null_if_not_found. */ + if (opt & FE_NNF) + retval = NULL; + else if (opt & FE_NATIVE) + buf.check (name); + else + retval = name; + + out: + if (posix) + buf.set_path (posix); + debug_printf ("%s = find_exec (%s)", (char *) buf, name); + if (known_suffix) + *known_suffix = suffix ?: strchr (buf, '\0'); + return retval; +} + +/* Utility for spawn_guts. */ + +static HANDLE +handle (int n, int direction) +{ + fhandler_base *fh = cygheap->fdtab[n]; + + if (!fh) + return INVALID_HANDLE_VALUE; + if (fh->close_on_exec ()) + return INVALID_HANDLE_VALUE; + if (direction == 0) + return fh->get_handle (); + return fh->get_output_handle (); +} + +int +iscmd (const char *argv0, const char *what) +{ + int n; + n = strlen (argv0) - strlen (what); + if (n >= 2 && argv0[1] != ':') + return 0; + return n >= 0 && strcasematch (argv0 + n, what) && + (n == 0 || isdirsep (argv0[n - 1])); +} + +class linebuf +{ + public: + size_t ix; + char *buf; + size_t alloced; + linebuf () : ix (0), buf (NULL), alloced (0) {} + ~linebuf () {if (buf) free (buf);} + void add (const char *what, int len); + void add (const char *what) {add (what, strlen (what));} + void prepend (const char *what, int len); +}; + +void +linebuf::add (const char *what, int len) +{ + size_t newix; + if ((newix = ix + len) >= alloced || !buf) + { + alloced += LINE_BUF_CHUNK + newix; + buf = (char *) realloc (buf, alloced + 1); + } + memcpy (buf + ix, what, len); + ix = newix; + buf[ix] = '\0'; +} + +void +linebuf::prepend (const char *what, int len) +{ + int buflen; + size_t newix; + if ((newix = ix + len) >= alloced) + { + alloced += LINE_BUF_CHUNK + newix; + buf = (char *) realloc (buf, alloced + 1); + buf[ix] = '\0'; + } + if ((buflen = strlen (buf))) + memmove (buf + len, buf, buflen + 1); + else + buf[newix] = '\0'; + memcpy (buf, what, len); + ix = newix; +} + +class av +{ + char **argv; + int calloced; + public: + int error; + int argc; + av (int ac, const char * const *av) : calloced (0), error (false), argc (ac) + { + argv = (char **) cmalloc (HEAP_1_ARGV, (argc + 5) * sizeof (char *)); + memcpy (argv, av, (argc + 1) * sizeof (char *)); + } + ~av () + { + if (argv) + { + for (int i = 0; i < calloced; i++) + if (argv[i]) + cfree (argv[i]); + cfree (argv); + } + } + int unshift (const char *what, int conv = 0); + operator char **() {return argv;} + void all_calloced () {calloced = argc;} + void replace0_maybe (const char *arg0) + { + /* Note: Assumes that argv array has not yet been "unshifted" */ + if (!calloced + && (argv[0] = cstrdup1 (arg0))) + calloced = true; + else + error = errno; + } + void dup_maybe (int i) + { + if (i >= calloced + && !(argv[i] = cstrdup1 (argv[i]))) + error = errno; + } + void dup_all () + { + for (int i = calloced; i < argc; i++) + if (!(argv[i] = cstrdup1 (argv[i]))) + error = errno; + } +}; + +int +av::unshift (const char *what, int conv) +{ + char **av; + av = (char **) crealloc (argv, (argc + 2) * sizeof (char *)); + if (!av) + return 0; + + argv = av; + memmove (argv + 1, argv, (argc + 1) * sizeof (char *)); + char buf[CYG_MAX_PATH + 1]; + if (conv) + { + cygwin_conv_to_posix_path (what, buf); + char *p = strchr (buf, '\0') - 4; + if (p > buf && strcasematch (p, ".exe")) + *p = '\0'; + what = buf; + } + if (!(*argv = cstrdup1 (what))) + error = errno; + argc++; + calloced++; + return 1; +} + +struct pthread_cleanup +{ + _sig_func_ptr oldint; + _sig_func_ptr oldquit; + sigset_t oldmask; + pthread_cleanup (): oldint (NULL), oldquit (NULL), oldmask ((sigset_t) -1) {} +}; + +static void +do_cleanup (void *args) +{ +# define cleanup ((pthread_cleanup *) args) + if (cleanup->oldint) + signal (SIGINT, cleanup->oldint); + if (cleanup->oldquit) + signal (SIGQUIT, cleanup->oldquit); + if (cleanup->oldmask != (sigset_t) -1) + sigprocmask (SIG_SETMASK, &(cleanup->oldmask), NULL); +# undef cleanup +} + + +static int __stdcall +spawn_guts (const char * prog_arg, const char *const *argv, + const char *const envp[], int mode) +{ + bool rc; + pid_t cygpid; + + MALLOC_CHECK; + + if (prog_arg == NULL) + { + syscall_printf ("prog_arg is NULL"); + set_errno (EINVAL); + return -1; + } + + syscall_printf ("spawn_guts (%d, %.9500s)", mode, prog_arg); + + if (argv == NULL) + { + syscall_printf ("argv is NULL"); + set_errno (EINVAL); + return -1; + } + + path_conv real_path; + + linebuf one_line; + + STARTUPINFO si = {0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}; + + child_info_spawn ciresrv; + si.lpReserved2 = (LPBYTE) &ciresrv; + si.cbReserved2 = sizeof (ciresrv); + + DWORD chtype; + if (mode != _P_OVERLAY) + chtype = PROC_SPAWN; + else + chtype = PROC_EXEC; + + HANDLE subproc_ready; + if (1 || chtype != PROC_EXEC) + subproc_ready = NULL; + else + { + subproc_ready = CreateEvent (&sec_all, TRUE, FALSE, NULL); + ProtectHandleINH (subproc_ready); + } + + init_child_info (chtype, &ciresrv, subproc_ready); + + ciresrv.moreinfo = (cygheap_exec_info *) ccalloc (HEAP_1_EXEC, 1, sizeof (cygheap_exec_info)); + ciresrv.moreinfo->old_title = NULL; + + /* CreateProcess takes one long string that is the command line (sigh). + We need to quote any argument that has whitespace or embedded "'s. */ + + int ac; + for (ac = 0; argv[ac]; ac++) + /* nothing */; + + av newargv (ac, argv); + + int null_app_name = 0; + if (ac == 3 && argv[1][0] == '/' && argv[1][1] == 'c' && + (iscmd (argv[0], "command.com") || iscmd (argv[0], "cmd.exe"))) + { + real_path.check (prog_arg); + one_line.add ("\""); + if (!real_path.error) + one_line.add (real_path); + else + one_line.add (argv[0]); + one_line.add ("\""); + one_line.add (" "); + one_line.add (argv[1]); + one_line.add (" "); + one_line.add (argv[2]); + strcpy (real_path, argv[0]); + null_app_name = 1; + goto skip_arg_parsing; + } + + const char *ext; + if ((ext = perhaps_suffix (prog_arg, real_path)) == NULL) + { + set_errno (ENOENT); + return -1; + } + + MALLOC_CHECK; + + /* If the file name ends in either .exe, .com, .bat, or .cmd we assume + that it is NOT a script file */ + while (*ext == '\0') + { + HANDLE hnd = CreateFile (real_path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sec_none_nih, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0); + if (hnd == INVALID_HANDLE_VALUE) + { + __seterrno (); + return -1; + } + + DWORD done; + + char buf[2 * CYG_MAX_PATH + 1]; + buf[0] = buf[1] = buf[2] = buf[sizeof (buf) - 1] = '\0'; + if (!ReadFile (hnd, buf, sizeof (buf) - 1, &done, 0)) + { + CloseHandle (hnd); + __seterrno (); + return -1; + } + + CloseHandle (hnd); + + if (buf[0] == 'M' && buf[1] == 'Z') + break; + + debug_printf ("%s is a script", (char *) real_path); + + if (real_path.has_acls () && allow_ntsec + && check_file_access (real_path, X_OK)) + { + debug_printf ("... but not executable"); + break; + } + + char *pgm, *arg1; + + if (buf[0] != '#' || buf[1] != '!') + { + pgm = (char *) "/bin/sh"; + arg1 = NULL; + } + else + { + char *ptr; + pgm = buf + 2; + pgm += strspn (pgm, " \t"); + for (ptr = pgm, arg1 = NULL; + *ptr && *ptr != '\r' && *ptr != '\n'; + ptr++) + if (!arg1 && (*ptr == ' ' || *ptr == '\t')) + { + /* Null terminate the initial command and step over + any additional white space. If we've hit the + end of the line, exit the loop. Otherwise, we've + found the first argument. Position the current + pointer on the last known white space. */ + *ptr = '\0'; + char *newptr = ptr + 1; + newptr += strspn (newptr, " \t"); + if (!*newptr || *newptr == '\r' || *newptr == '\n') + break; + arg1 = newptr; + ptr = newptr - 1; + } + + *ptr = '\0'; + } + + /* Replace argv[0] with the full path to the script if this is the + first time through the loop. */ + newargv.replace0_maybe (prog_arg); + + /* pointers: + * pgm interpreter name + * arg1 optional string + */ + if (arg1) + newargv.unshift (arg1); + + /* FIXME: This should not be using FE_NATIVE. It should be putting + the posix path on the argv list. */ + find_exec (pgm, real_path, "PATH=", FE_NATIVE, &ext); + newargv.unshift (real_path, 1); + } + + if (real_path.iscygexec ()) + newargv.dup_all (); + else + { + for (int i = 0; i < newargv.argc; i++) + { + char *p = NULL; + const char *a; + + newargv.dup_maybe (i); + a = i ? newargv[i] : (char *) real_path; + int len = strlen (a); + if (len != 0 && !strpbrk (a, " \t\n\r\"")) + one_line.add (a, len); + else + { + one_line.add ("\"", 1); + /* Handle embedded special characters " and \. + A " is always preceded by a \. + A \ is not special unless it precedes a ". If it does, + then all preceding \'s must be doubled to avoid having + the Windows command line parser interpret the \ as quoting + the ". This rule applies to a string of \'s before the end + of the string, since cygwin/windows uses a " to delimit the + argument. */ + for (; (p = strpbrk (a, "\"\\")); a = ++p) + { + one_line.add (a, p - a); + /* Find length of string of backslashes */ + int n = strspn (p, "\\"); + if (!n) + one_line.add ("\\\"", 2); /* No backslashes, so it must be a ". + The " has to be protected with a backslash. */ + else + { + one_line.add (p, n); /* Add the run of backslashes */ + /* Need to double up all of the preceding + backslashes if they precede a quote or EOS. */ + if (!p[n] || p[n] == '"') + one_line.add (p, n); + p += n - 1; /* Point to last backslash */ + } + } + if (*a) + one_line.add (a); + one_line.add ("\"", 1); + } + MALLOC_CHECK; + one_line.add (" ", 1); + MALLOC_CHECK; + } + + MALLOC_CHECK; + if (one_line.ix) + one_line.buf[one_line.ix - 1] = '\0'; + else + one_line.add ("", 1); + MALLOC_CHECK; + + if (one_line.ix > 32767) + { + debug_printf ("Command line too long (>32K), return E2BIG"); + set_errno (E2BIG); + return -1; + } + } + + char *envblock; + newargv.all_calloced (); + if (newargv.error) + { + set_errno (newargv.error); + return -1; + } + + ciresrv.moreinfo->argc = newargv.argc; + ciresrv.moreinfo->argv = newargv; + ciresrv.hexec_proc = hexec_proc; + + if (mode != _P_OVERLAY || + !DuplicateHandle (hMainProc, myself.shared_handle (), hMainProc, + &ciresrv.moreinfo->myself_pinfo, 0, + TRUE, DUPLICATE_SAME_ACCESS)) + ciresrv.moreinfo->myself_pinfo = NULL; + else + VerifyHandle (ciresrv.moreinfo->myself_pinfo); + + skip_arg_parsing: + PROCESS_INFORMATION pi = {NULL, 0, 0, 0}; + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = handle (0, 0); /* Get input handle */ + si.hStdOutput = handle (1, 1); /* Get output handle */ + si.hStdError = handle (2, 1); /* Get output handle */ + si.cb = sizeof (si); + + int flags = CREATE_DEFAULT_ERROR_MODE | GetPriorityClass (hMainProc); + + if (mode == _P_DETACH || !set_console_state_for_spawn ()) + flags |= DETACHED_PROCESS; + if (mode != _P_OVERLAY) + flags |= CREATE_SUSPENDED; +#if 0 //someday + else + myself->dwProcessId = 0; +#endif + + /* Some file types (currently only sockets) need extra effort in the + parent after CreateProcess and before copying the datastructures + to the child. So we have to start the child in suspend state, + unfortunately, to avoid a race condition. */ + if (cygheap->fdtab.need_fixup_before ()) + flags |= CREATE_SUSPENDED; + + const char *runpath = null_app_name ? NULL : (const char *) real_path; + + syscall_printf ("null_app_name %d (%s, %.9500s)", null_app_name, runpath, one_line.buf); + + void *newheap; + + cygbench ("spawn-guts"); + + cygheap->fdtab.set_file_pointers_for_exec (); + cygheap->user.deimpersonate (); + + /* When ruid != euid we create the new process under the current original + account and impersonate in child, this way maintaining the different + effective vs. real ids. + FIXME: If ruid != euid and ruid != saved_uid we currently give + up on ruid. The new process will have ruid == euid. */ + if (!cygheap->user.issetuid () + || (cygheap->user.saved_uid == cygheap->user.real_uid + && cygheap->user.saved_gid == cygheap->user.real_gid + && !cygheap->user.groups.issetgroups ())) + { + ciresrv.moreinfo->envp = build_env (envp, envblock, ciresrv.moreinfo->envc, + real_path.iscygexec ()); + newheap = cygheap_setup_for_child (&ciresrv, cygheap->fdtab.need_fixup_before ()); + rc = CreateProcess (runpath, /* image name - with full path */ + one_line.buf, /* what was passed to exec */ + &sec_none_nih,/* process security attrs */ + &sec_none_nih,/* thread security attrs */ + TRUE, /* inherit handles from parent */ + flags, + envblock, /* environment */ + 0, /* use current drive/directory */ + &si, + &pi); + } + else + { + /* Give access to myself */ + if (mode == _P_OVERLAY) + myself.set_acl(); + + /* allow the child to interact with our window station/desktop */ + HANDLE hwst, hdsk; + SECURITY_INFORMATION dsi = DACL_SECURITY_INFORMATION; + DWORD n; + char wstname[1024]; + char dskname[1024]; + + hwst = GetProcessWindowStation (); + SetUserObjectSecurity (hwst, &dsi, get_null_sd ()); + GetUserObjectInformation (hwst, UOI_NAME, wstname, 1024, &n); + hdsk = GetThreadDesktop (GetCurrentThreadId ()); + SetUserObjectSecurity (hdsk, &dsi, get_null_sd ()); + GetUserObjectInformation (hdsk, UOI_NAME, dskname, 1024, &n); + strcat (wstname, "\\"); + strcat (wstname, dskname); + si.lpDesktop = wstname; + + ciresrv.moreinfo->envp = build_env (envp, envblock, ciresrv.moreinfo->envc, + real_path.iscygexec ()); + newheap = cygheap_setup_for_child (&ciresrv, cygheap->fdtab.need_fixup_before ()); + rc = CreateProcessAsUser (cygheap->user.token (), + runpath, /* image name - with full path */ + one_line.buf, /* what was passed to exec */ + &sec_none_nih, /* process security attrs */ + &sec_none_nih, /* thread security attrs */ + TRUE, /* inherit handles from parent */ + flags, + envblock, /* environment */ + 0, /* use current drive/directory */ + &si, + &pi); + } + + /* Restore impersonation. In case of _P_OVERLAY this isn't + allowed since it would overwrite child data. */ + if (mode != _P_OVERLAY || !rc) + cygheap->user.reimpersonate (); + + MALLOC_CHECK; + if (envblock) + free (envblock); + MALLOC_CHECK; + + /* Set errno now so that debugging messages from it appear before our + final debugging message [this is a general rule for debugging + messages]. */ + if (!rc) + { + __seterrno (); + syscall_printf ("CreateProcess failed, %E"); +#if 0 // someday + if (mode == _P_OVERLAY) + myself->dwProcessId = GetCurrentProcessId (); +#endif + if (subproc_ready) + ForceCloseHandle (subproc_ready); + cygheap_setup_for_child_cleanup (newheap, &ciresrv, 0); + return -1; + } + + /* FIXME: There is a small race here */ + + int res; + pthread_cleanup cleanup; + pthread_cleanup_push (do_cleanup, (void *) &cleanup); + if (mode == _P_SYSTEM) + { + sigset_t child_block; + cleanup.oldint = signal (SIGINT, SIG_IGN); + cleanup.oldquit = signal (SIGQUIT, SIG_IGN); + sigemptyset (&child_block); + sigaddset (&child_block, SIGCHLD); + (void) sigprocmask (SIG_BLOCK, &child_block, &cleanup.oldmask); + } + + /* Fixup the parent data structures if needed and resume the child's + main thread. */ + if (!cygheap->fdtab.need_fixup_before ()) + cygheap_setup_for_child_cleanup (newheap, &ciresrv, 0); + else + { + cygheap->fdtab.fixup_before_exec (pi.dwProcessId); + cygheap_setup_for_child_cleanup (newheap, &ciresrv, 1); + if (mode == _P_OVERLAY) + { + ResumeThread (pi.hThread); + cygthread::terminate (); + } + } + + if (mode != _P_OVERLAY) + cygpid = cygwin_pid (pi.dwProcessId); + else + cygpid = myself->pid; + + /* We print the original program name here so the user can see that too. */ + syscall_printf ("%d = spawn_guts (%s, %.9500s)", + rc ? cygpid : (unsigned int) -1, prog_arg, one_line.buf); + + /* Name the handle similarly to proc_subproc. */ + ProtectHandle1 (pi.hProcess, childhProc); + + if (mode == _P_OVERLAY) + { + /* These are both duplicated in the child code. We do this here, + primarily for strace. */ + strace.execing = 1; + hExeced = pi.hProcess; + dwExeced = pi.dwProcessId; + strcpy (myself->progname, real_path); + close_all_files (); + } + else + { + myself->set_has_pgid_children (); + ProtectHandle (pi.hThread); + pinfo child (cygpid, PID_IN_USE); + if (!child) + { + syscall_printf ("pinfo failed"); + if (get_errno () != ENOMEM) + set_errno (EAGAIN); + res = -1; + goto out; + } + child->dwProcessId = pi.dwProcessId; + child.hProcess = pi.hProcess; + if (!child.remember ()) + { + syscall_printf ("process table full"); + set_errno (EAGAIN); + res = -1; + goto out; + } + + strcpy (child->progname, real_path); + /* FIXME: This introduces an unreferenced, open handle into the child. + The purpose is to keep the pid shared memory open so that all of + the fields filled out by child.remember do not disappear and so there + is not a brief period during which the pid is not available. + However, we should try to find another way to do this eventually. */ + (void) DuplicateHandle (hMainProc, child.shared_handle (), pi.hProcess, + NULL, 0, 0, DUPLICATE_SAME_ACCESS); + /* Start the child running */ + ResumeThread (pi.hThread); + } + + ForceCloseHandle (pi.hThread); + + sigproc_printf ("spawned windows pid %d", pi.dwProcessId); + + bool exited; + + res = 0; + exited = false; +#if 0 + if (mode == _P_OVERLAY) + { + int nwait = 3; + HANDLE waitbuf[3] = {pi.hProcess, signal_arrived, subproc_ready}; + for (int i = 0; i < 100; i++) + { + switch (WaitForMultipleObjects (nwait, waitbuf, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + sigproc_printf ("subprocess exited"); + DWORD exitcode; + if (!GetExitCodeProcess (pi.hProcess, &exitcode)) + exitcode = 1; + res |= exitcode; + exited = true; + break; + case WAIT_OBJECT_0 + 1: + sigproc_printf ("signal arrived"); + reset_signal_arrived (); + continue; + case WAIT_OBJECT_0 + 2: + if (!myself->cygstarted) + { + nwait = 2; + sigproc_terminate (); + continue; + } + break; + case WAIT_FAILED: + system_printf ("wait failed: nwait %d, pid %d, winpid %d, %E", + nwait, myself->pid, myself->dwProcessId); + system_printf ("waitbuf[0] %p %d", waitbuf[0], + WaitForSingleObject (waitbuf[0], 0)); + system_printf ("waitbuf[1] %p %d", waitbuf[1], + WaitForSingleObject (waitbuf[1], 0)); + system_printf ("waitbuf[w] %p %d", waitbuf[2], + WaitForSingleObject (waitbuf[2], 0)); + set_errno (ECHILD); + try_to_debug (); + return -1; + } + break; + } + + ForceCloseHandle (subproc_ready); + sigproc_printf ("P_OVERLAY res %p", res); + } +#endif + + ForceCloseHandle1 (pi.hProcess, childhProc); + + switch (mode) + { + case _P_OVERLAY: + myself->exit (res, 1); + break; + case _P_WAIT: + case _P_SYSTEM: + if (waitpid (cygpid, (int *) &res, 0) != cygpid) + res = -1; + break; + case _P_DETACH: + res = 0; /* Lose all memory of this child. */ + break; + case _P_NOWAIT: + case _P_NOWAITO: + case _P_VFORK: + res = cygpid; + break; + default: + break; + } + +out: + pthread_cleanup_pop (1); + return (int) res; +} + +extern "C" int +cwait (int *result, int pid, int) +{ + return waitpid (pid, result, 0); +} + +/* + * Helper function for spawn runtime calls. + * Doesn't search the path. + */ + +extern "C" int +spawnve (int mode, const char *path, const char *const *argv, + const char *const *envp) +{ + int ret; +#ifdef NEWVFORK + vfork_save *vf = vfork_storage.val (); + + if (vf != NULL && (vf->pid < 0) && mode == _P_OVERLAY) + mode = _P_NOWAIT; + else + vf = NULL; +#endif + + syscall_printf ("spawnve (%s, %s, %x)", path, argv[0], envp); + + switch (mode) + { + case _P_OVERLAY: + /* We do not pass _P_SEARCH_PATH here. execve doesn't search PATH.*/ + /* Just act as an exec if _P_OVERLAY set. */ + spawn_guts (path, argv, envp, mode); + /* Errno should be set by spawn_guts. */ + ret = -1; + break; + case _P_VFORK: + case _P_NOWAIT: + case _P_NOWAITO: + case _P_WAIT: + case _P_DETACH: + case _P_SYSTEM: + ret = spawn_guts (path, argv, envp, mode); +#ifdef NEWVFORK + if (vf) + { + if (ret > 0) + { + debug_printf ("longjmping due to vfork"); + vf->restore_pid (ret); + } + } +#endif + break; + default: + set_errno (EINVAL); + ret = -1; + break; + } + return ret; +} + +/* + * spawn functions as implemented in the MS runtime library. + * Most of these based on (and copied from) newlib/libc/posix/execXX.c + */ + +extern "C" int +spawnl (int mode, const char *path, const char *arg0, ...) +{ + int i; + va_list args; + const char *argv[256]; + + va_start (args, arg0); + argv[0] = arg0; + i = 1; + + do + argv[i] = va_arg (args, const char *); + while (argv[i++] != NULL); + + va_end (args); + + return spawnve (mode, path, (char * const *) argv, cur_environ ()); +} + +extern "C" int +spawnle (int mode, const char *path, const char *arg0, ...) +{ + int i; + va_list args; + const char * const *envp; + const char *argv[256]; + + va_start (args, arg0); + argv[0] = arg0; + i = 1; + + do + argv[i] = va_arg (args, const char *); + while (argv[i++] != NULL); + + envp = va_arg (args, const char * const *); + va_end (args); + + return spawnve (mode, path, (char * const *) argv, (char * const *) envp); +} + +extern "C" int +spawnlp (int mode, const char *path, const char *arg0, ...) +{ + int i; + va_list args; + const char *argv[256]; + + va_start (args, arg0); + argv[0] = arg0; + i = 1; + + do + argv[i] = va_arg (args, const char *); + while (argv[i++] != NULL); + + va_end (args); + + return spawnvpe (mode, path, (char * const *) argv, cur_environ ()); +} + +extern "C" int +spawnlpe (int mode, const char *path, const char *arg0, ...) +{ + int i; + va_list args; + const char * const *envp; + const char *argv[256]; + + va_start (args, arg0); + argv[0] = arg0; + i = 1; + + do + argv[i] = va_arg (args, const char *); + while (argv[i++] != NULL); + + envp = va_arg (args, const char * const *); + va_end (args); + + return spawnvpe (mode, path, (char * const *) argv, envp); +} + +extern "C" int +spawnv (int mode, const char *path, const char * const *argv) +{ + return spawnve (mode, path, argv, cur_environ ()); +} + +extern "C" int +spawnvp (int mode, const char *path, const char * const *argv) +{ + return spawnvpe (mode, path, argv, cur_environ ()); +} + +extern "C" int +spawnvpe (int mode, const char *file, const char * const *argv, + const char * const *envp) +{ + path_conv buf; + return spawnve (mode, find_exec (file, buf), argv, envp); +} diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc new file mode 100644 index 00000000000..ef77bd527f2 --- /dev/null +++ b/winsup/cygwin/tty.cc @@ -0,0 +1,494 @@ +/* tty.cc + + Copyright 1997, 1998, 2000, 2001, 2002 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include +#include +#include +#include +#include +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "pinfo.h" +#include "cygserver.h" +#include "shared_info.h" +#include "cygthread.h" + +extern fhandler_tty_master *tty_master; + +extern "C" int +grantpt (int fd) +{ + return 0; +} + +extern "C" int +unlockpt (int fd) +{ + return 0; +} + +extern "C" int +revoke (char *ttyname) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" int +ttyslot (void) +{ + if (NOTSTATE (myself, PID_USETTY)) + return -1; + return myself->ctty; +} + +void __stdcall +tty_init (void) +{ + if (!myself->cygstarted && NOTSTATE (myself, PID_CYGPARENT)) + cygheap->fdtab.get_debugger_info (); + + if (NOTSTATE (myself, PID_USETTY)) + return; + if (myself->ctty == -1) + if (NOTSTATE (myself, PID_CYGPARENT)) + myself->ctty = attach_tty (myself->ctty); + else + return; + if (myself->ctty == -1) + termios_printf ("Can't attach to tty"); +} + +/* Create session's master tty */ + +void __stdcall +create_tty_master (int ttynum) +{ + device ttym = *ttym_dev; + ttym.setunit (ttynum); /* CGF FIXME device */ + tty_master = (fhandler_tty_master *) build_fh_dev (ttym); + if (tty_master->init ()) + api_fatal ("Can't create master tty"); + else + { + /* Log utmp entry */ + struct utmp our_utmp; + DWORD len = sizeof our_utmp.ut_host; + + bzero ((char *) &our_utmp, sizeof (utmp)); + (void) time (&our_utmp.ut_time); + strncpy (our_utmp.ut_name, getlogin (), sizeof (our_utmp.ut_name)); + GetComputerName (our_utmp.ut_host, &len); + __small_sprintf (our_utmp.ut_line, "tty%d", ttynum); + if ((len = strlen (our_utmp.ut_line)) >= UT_IDLEN) + len -= UT_IDLEN; + else + len = 0; + strncpy (our_utmp.ut_id, our_utmp.ut_line + len, UT_IDLEN); + our_utmp.ut_type = USER_PROCESS; + our_utmp.ut_pid = myself->pid; + myself->ctty = ttynum; + login (&our_utmp); + } +} + +void __stdcall +tty_terminate (void) +{ + if (NOTSTATE (myself, PID_USETTY)) + return; + cygwin_shared->tty.terminate (); +} + +int __stdcall +attach_tty (int num) +{ + if (num != -1) + { + return cygwin_shared->tty.connect_tty (num); + } + if (NOTSTATE (myself, PID_USETTY)) + return -1; + return cygwin_shared->tty.allocate_tty (true); +} + +void +tty_list::terminate (void) +{ + int ttynum = myself->ctty; + + /* Keep master running till there are connected clients */ + if (ttynum != -1 && ttys[ttynum].master_pid == GetCurrentProcessId ()) + { + tty *t = ttys + ttynum; + CloseHandle (t->from_master); + CloseHandle (t->to_master); + /* Wait for children which rely on tty handling in this process to + go away */ + for (int i = 0; ; i++) + { + if (!t->slave_alive ()) + break; + if (i >= 100) + { + small_printf ("waiting for children using tty%d to terminate\n", + ttynum); + i = 0; + } + + low_priority_sleep (200); + } + + termios_printf ("tty %d master about to finish", ttynum); + ForceCloseHandle1 (t->to_slave, to_pty); + ForceCloseHandle1 (t->from_slave, from_pty); + CloseHandle (tty_master->inuse); + t->init (); + + char buf[20]; + __small_sprintf (buf, "tty%d", ttynum); + logout (buf); + } +} + +int +tty_list::connect_tty (int ttynum) +{ + if (ttynum < 0 || ttynum >= NTTYS) + { + termios_printf ("ttynum (%d) out of range", ttynum); + return -1; + } + if (!ttys[ttynum].exists ()) + { + termios_printf ("tty %d was not allocated", ttynum); + return -1; + } + + return ttynum; +} + +void +tty_list::init (void) +{ + for (int i = 0; i < NTTYS; i++) + { + ttys[i].init (); + ttys[i].setntty (i); + } +} + +/* Search for tty class for our console. Allocate new tty if our process is + the only cygwin process in the current console. + Return tty number or -1 if error. + If flag == 0, just find a free tty. + */ +int +tty_list::allocate_tty (bool with_console) +{ + HWND console; + int freetty = -1; + HANDLE hmaster = NULL; + + /* FIXME: This whole function needs a protective mutex. */ + + if (WaitForSingleObject (tty_mutex, INFINITE) == WAIT_FAILED) + termios_printf ("WFSO for tty_mutex %p failed, %E", tty_mutex); + + if (!with_console) + console = NULL; + else if (!(console = GetConsoleWindow ())) + { + char oldtitle[TITLESIZE]; + + if (!GetConsoleTitle (oldtitle, TITLESIZE)) + { + termios_printf ("Can't read console title"); + goto out; + } + + char buf[40]; + + __small_sprintf (buf, "cygwin.find.console.%d", myself->pid); + SetConsoleTitle (buf); + for (int times = 0; times < 25; times++) + { + Sleep (10); + if ((console = FindWindow (NULL, buf))) + break; + } + SetConsoleTitle (oldtitle); + Sleep (40); + if (console == NULL) + { + termios_printf ("Can't find console window"); + goto out; + } + } + + /* Is a tty allocated for console? */ + for (int i = 0; i < NTTYS; i++) + { + if (!ttys[i].exists ()) + { + if (freetty < 0) /* Scanning? */ + freetty = i; /* Yes. */ + if (!with_console) /* Do we want to attach this to a console? */ + break; /* No. We've got one. */ + } + + /* FIXME: Is this right? We can potentially query a "nonexistent" + tty slot after falling through from the above? */ + if (with_console && ttys[i].gethwnd () == console) + { + termios_printf ("console %x already associated with tty%d", + console, i); + /* Is the master alive? */ + hmaster = OpenProcess (PROCESS_DUP_HANDLE, FALSE, ttys[i].master_pid); + if (hmaster) + { + CloseHandle (hmaster); + freetty = i; + goto out; + } + /* Master is dead */ + freetty = i; + break; + } + } + + /* There is no tty allocated to console, allocate the first free found */ + if (freetty == -1) + goto out; + + tty *t; + t = ttys + freetty; + t->init (); + t->setsid (-1); + t->setpgid (myself->pgid); + t->sethwnd (console); + +out: + if (freetty < 0) + { + ReleaseMutex (tty_mutex); + system_printf ("No tty allocated"); + } + else if (!with_console) + { + termios_printf ("tty%d allocated", freetty); + /* exit with tty_mutex still held -- caller has more work to do */ + } + else + { + termios_printf ("console %p associated with tty%d", console, freetty); + if (!hmaster) + create_tty_master (freetty); + ReleaseMutex (tty_mutex); + } + return freetty; +} + +bool +tty::slave_alive () +{ + return alive (TTY_SLAVE_ALIVE); +} + +bool +tty::master_alive () +{ + return alive (TTY_MASTER_ALIVE); +} + +bool +tty::alive (const char *fmt) +{ + HANDLE ev; + char buf[CYG_MAX_PATH]; + + shared_name (buf, fmt, ntty); + if ((ev = OpenEvent (EVENT_ALL_ACCESS, FALSE, buf))) + CloseHandle (ev); + return ev != NULL; +} + +HANDLE +tty::open_output_mutex () +{ + return open_mutex (OUTPUT_MUTEX); +} + +HANDLE +tty::open_input_mutex () +{ + return open_mutex (INPUT_MUTEX); +} + +HANDLE +tty::open_mutex (const char *mutex) +{ + char buf[CYG_MAX_PATH]; + shared_name (buf, mutex, ntty); + return OpenMutex (MUTEX_ALL_ACCESS, TRUE, buf); +} + +HANDLE +tty::create_inuse (const char *fmt) +{ + HANDLE h; + char buf[CYG_MAX_PATH]; + + shared_name (buf, fmt, ntty); + h = CreateEvent (&sec_all, TRUE, FALSE, buf); + termios_printf ("%s %p", buf, h); + if (!h) + termios_printf ("couldn't open inuse event, %E", buf); + return h; +} + +void +tty::init (void) +{ + output_stopped = 0; + setsid (0); + pgid = 0; + hwnd = NULL; + to_slave = NULL; + from_slave = NULL; + was_opened = 0; +} + +HANDLE +tty::get_event (const char *fmt, BOOL manual_reset) +{ + HANDLE hev; + char buf[CYG_MAX_PATH]; + + shared_name (buf, fmt, ntty); + if (!(hev = CreateEvent (&sec_all, manual_reset, FALSE, buf))) + { + termios_printf ("couldn't create %s", buf); + set_errno (ENOENT); /* FIXME this can't be the right errno */ + return NULL; + } + + termios_printf ("created event %s", buf); + return hev; +} + +bool +tty::make_pipes (fhandler_pty_master *ptym) +{ + /* Create communication pipes */ + + /* FIXME: should this be sec_none_nih? */ + if (!CreatePipe (&from_master, &to_slave, &sec_all, 128 * 1024)) + { + termios_printf ("can't create input pipe"); + set_errno (ENOENT); + return false; + } + + // ProtectHandle1INH (to_slave, to_pty); + if (!CreatePipe (&from_slave, &to_master, &sec_all, 128 * 1024)) + { + termios_printf ("can't create output pipe"); + set_errno (ENOENT); + return false; + } + // ProtectHandle1INH (from_slave, from_pty); + termios_printf ("tty%d from_slave %p, to_slave %p", ntty, from_slave, + to_slave); + + DWORD pipe_mode = PIPE_NOWAIT; + if (!SetNamedPipeHandleState (to_slave, &pipe_mode, NULL, NULL)) + termios_printf ("can't set to_slave to non-blocking mode"); + ptym->set_io_handle (from_slave); + ptym->set_output_handle (to_slave); + return true; +} + +bool +tty::common_init (fhandler_pty_master *ptym) +{ + /* Set termios information. Force initialization. */ + ptym->tcinit (this, true); + + if (!make_pipes (ptym)) + return false; + ptym->need_nl = 0; + + /* Save our pid */ + + master_pid = GetCurrentProcessId (); + + /* We do not open allow the others to open us (for handle duplication) + but rely on cygheap->inherited_ctty for descendant processes. + In the future the cygserver may allow access by others. */ + +#ifdef USE_SERVER + if (wincap.has_security ()) + { + if (cygserver_running == CYGSERVER_UNKNOWN) + cygserver_init (); + } +#endif + + /* Create synchronisation events */ + + if (ptym->get_major () != DEV_TTYM_MAJOR) + { + ptym->output_done_event = ptym->ioctl_done_event = + ptym->ioctl_request_event = NULL; + } + else + { + if (!(ptym->output_done_event = get_event (OUTPUT_DONE_EVENT))) + return false; + if (!(ptym->ioctl_done_event = get_event (IOCTL_DONE_EVENT))) + return false; + if (!(ptym->ioctl_request_event = get_event (IOCTL_REQUEST_EVENT))) + return false; + } + + if (!(ptym->input_available_event = get_event (INPUT_AVAILABLE_EVENT, TRUE))) + return false; + + char buf[CYG_MAX_PATH]; + shared_name (buf, OUTPUT_MUTEX, ntty); + if (!(ptym->output_mutex = CreateMutex (&sec_all, FALSE, buf))) + { + termios_printf ("can't create %s", buf); + set_errno (ENOENT); + return false; + } + + shared_name (buf, INPUT_MUTEX, ntty); + if (!(ptym->input_mutex = CreateMutex (&sec_all, FALSE, buf))) + { + termios_printf ("can't create %s", buf); + set_errno (ENOENT); + return false; + } + + // /* screws up tty master */ ProtectHandle1INH (ptym->output_mutex, output_mutex); + // /* screws up tty master */ ProtectHandle1INH (ptym->input_mutex, input_mutex); + winsize.ws_col = 80; + winsize.ws_row = 25; + + termios_printf ("tty%d opened", ntty); + return true; +} -- cgit v1.2.1