/* cygserver_process.cc Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins 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 #include #include #include #include #include #include #include #include "wincap.h" #include #include #include #define debug_printf if (DEBUG) printf #define DEBUG 1 /* the cache structures and classes are designed for one cache per server process. * To make multiple process caches, a redesign will be needed */ /* process cache */ process_cache::process_cache (unsigned int num_initial_workers): head (NULL) { /* there can only be one */ InitializeCriticalSection (&cache_write_access); if ((cache_add_trigger = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) { printf ("Failed to create cache add trigger (%lu), terminating\n", GetLastError ()); exit (1); } initial_workers = num_initial_workers; } process_cache::~process_cache () { } class process * process_cache::process (long pid) { class process *entry = head; /* TODO: make this more granular, so a search doesn't involve the write lock */ EnterCriticalSection (&cache_write_access); if (!entry) { entry = new class process (pid); entry->next = (class process *) InterlockedExchangePointer (&head, entry); PulseEvent (cache_add_trigger); } else { while (entry->winpid != pid && entry->next) entry = entry->next; if (entry->winpid != pid) { class process *new_entry = new class process (pid); new_entry->next = (class process *) InterlockedExchangePointer (&entry->next, new_entry); entry = new_entry; PulseEvent (cache_add_trigger); } } LeaveCriticalSection (&cache_write_access); return entry; } static DWORD WINAPI request_loop (LPVOID LpParam) { class process_process_param *params = (process_process_param *) LpParam; return params->request_loop (); } void process_cache::process_requests () { class process_process_param *params = new process_process_param; threaded_queue::process_requests (params, request_loop); } void process_cache::add_task (class process * theprocess) { /* safe to not "Try" because workers don't hog this, they wait on the event */ /* every derived ::add must enter the section! */ EnterCriticalSection (&queuelock); queue_request *listrequest = new process_cleanup (theprocess); threaded_queue::add (listrequest); LeaveCriticalSection (&queuelock); } /* NOT fully MT SAFE: must be called by only one thread in a program */ void process_cache::remove_process (class process *theprocess) { class process *entry = head; /* unlink */ EnterCriticalSection (&cache_write_access); if (entry == theprocess) { entry = (class process *) InterlockedExchangePointer (&head, theprocess->next); if (entry != theprocess) { printf ("Bug encountered, process cache corrupted\n"); exit (1); } } else { while (entry->next && entry->next != theprocess) entry = entry->next; class process *temp = (class process *) InterlockedExchangePointer (&entry->next, theprocess->next); if (temp != theprocess) { printf ("Bug encountered, process cache corrupted\n"); exit (1); } } LeaveCriticalSection (&cache_write_access); /* Process any cleanup tasks */ add_task (theprocess); } /* copy <= max_copy HANDLEs to dest[], starting at an offset into _our list_ of * begin_at. (Ie begin_at = 5, the first copied handle is still written to dest[0] * NOTE: Thread safe, but not thread guaranteed - a newly added process may be missed. * Who cares - It'll get caught the next time. */ int process_cache::handle_snapshot (HANDLE * hdest, class process ** edest, ssize_t max_copy, int begin_at) { /* TODO:? grab a delete-lock, to prevent deletes during this process ? */ class process *entry = head; int count = begin_at; /* skip begin_at entries */ while (entry && count) { if (entry->exit_code () == STILL_ACTIVE) count--; entry = entry->next; } /* hit the end of the list within begin_at entries */ if (count) return 0; HANDLE *hto = hdest; class process **eto = edest; while (entry && count < max_copy) { /* hack */ if (entry->exit_code () == STILL_ACTIVE) { *hto = entry->handle (); *eto = entry; count++; hto++; eto++; } entry = entry->next; } return count; } /* process's */ /* global process crit section */ static CRITICAL_SECTION process_access; static pthread_once_t process_init; void do_process_init (void) { InitializeCriticalSection (&process_access); /* we don't have a cache shutdown capability today */ } process::process (long pid): winpid (pid), next (NULL), cleaning_up (0), head (NULL), _exit_status (STILL_ACTIVE) { pthread_once (&process_init, do_process_init); EnterCriticalSection (&process_access); thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (!thehandle) { printf ("unable to obtain handle for new cache process %ld\n", pid); thehandle = INVALID_HANDLE_VALUE; } debug_printf ("Got handle %p for new cache process %ld\n", thehandle, pid); InitializeCriticalSection (&access); LeaveCriticalSection (&process_access); } process::~process () { DeleteCriticalSection (&access); } HANDLE process::handle () { // DWORD exitstate = exit_code (); // if (exitstate == STILL_ACTIVE) return thehandle; /* FIXME: call the cleanup list ? */ // CloseHandle (thehandle); // debug_printf ("Process id %ld has terminated, attempting to open a new handle\n", // winpid); // thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); // debug_printf ("Got handle %p when refreshing cache process %ld\n", thehandle, winpid); // /* FIXME: what if OpenProcess fails ? */ // if (thehandle) // { // _exit_status = STILL_ACTIVE; // exit_code (); // } // else // thehandle = INVALID_HANDLE_VALUE; // return thehandle; } DWORD process::exit_code () { if (_exit_status != STILL_ACTIVE) return _exit_status; bool err = GetExitCodeProcess (thehandle, &_exit_status); if (!err) { debug_printf ("Failed to retrieve exit code (%ld)\n", GetLastError ()); thehandle = INVALID_HANDLE_VALUE; return _exit_status; } else if (_exit_status == STILL_ACTIVE) return _exit_status; /* add new cleanup task etc etc ? */ return _exit_status; } /* this is single threaded. It's called after the process is removed from the cache, * but inserts may be attemped by worker threads that have a pointer to it */ void process::cleanup () { /* Serialize this */ EnterCriticalSection (&access); InterlockedIncrement (&(long)cleaning_up); class cleanup_routine *entry = head; while (entry) { class cleanup_routine *temp; entry->cleanup (winpid); temp = entry->next; delete entry; entry = temp; } LeaveCriticalSection (&access); } bool process::add_cleanup_routine (class cleanup_routine *new_cleanup) { if (cleaning_up) return false; EnterCriticalSection (&access); /* check that we didn't block with ::cleanup () * This rigmarole is to get around win9x's glaring missing TryEnterCriticalSection call * which would be a whole lot easier */ if (cleaning_up) { LeaveCriticalSection (&access); return false; } new_cleanup->next = head; head = new_cleanup; LeaveCriticalSection (&access); return true; } /* process_cleanup */ void process_cleanup::process () { theprocess->cleanup (); delete theprocess; } /* process_process_param */ DWORD process_process_param::request_loop () { process_cache *cache = (process_cache *) queue; /* always malloc one, so there is no special case in the loop */ ssize_t HandlesSize = 2; HANDLE *Handles = (HANDLE *) malloc (sizeof (HANDLE) * HandlesSize); process **Entries = (process **) malloc (sizeof (LPVOID) * HandlesSize); /* TODO: put [1] at the end as it will also get done if a process dies? */ Handles[0] = interrupt; Handles[1] = cache->cache_add_trigger; while (cache->active && !shutdown) { int copied; copied = -1; int offset; offset = 1; int count; count = 2; while ((copied == HandlesSize - 2 - offset) || copied < 0) { /* we need more storage to cope with all the HANDLES */ if (copied == HandlesSize - 2 - offset) { HANDLE *temp = (HANDLE *) realloc (Handles, sizeof (HANDLE) * HandlesSize + 10); if (!temp) { printf ("cannot allocate more storage for the handle array!\n"); exit (1); } Handles = temp; process **ptemp = (process **) realloc (Entries, sizeof (LPVOID) * HandlesSize + 10); if (!ptemp) { printf ("cannot allocate more storage for the handle array!\n"); exit (1); } Entries = ptemp; HandlesSize += 10; } offset += copied; copied = cache->handle_snapshot (&Handles[2], &Entries[2], HandlesSize - 2 - offset, offset); count += copied; } debug_printf ("waiting on %u objects\n", count); DWORD rc = WaitForMultipleObjects (count, Handles, FALSE, INFINITE); if (rc == WAIT_FAILED) { printf ("Could not wait on the process handles (%ld)!\n", GetLastError ()); exit (1); } int objindex = rc - WAIT_OBJECT_0; if (objindex > 1 && objindex < count) { debug_printf ("Process %ld has left the building\n", Entries[objindex]->winpid); /* fire off the termination routines */ cache->remove_process (Entries[objindex]); } else if (objindex >= 0 && objindex < 2) { /* 0 is shutdown - do nothing */ /* 1 is a cache add event - just rebuild the object list */ } else { printf ("unexpected return code from WaitForMultiple objects in process_process_param::request_loop\n"); } } running = false; return 0; }