diff src/process-nt.c @ 280:7df0dd720c89 r21-0b38

Import from CVS: tag r21-0b38
author cvs
date Mon, 13 Aug 2007 10:32:22 +0200
parents 90d73dddcdc4
children c42ec1d1cded
line wrap: on
line diff
--- a/src/process-nt.c	Mon Aug 13 10:31:30 2007 +0200
+++ b/src/process-nt.c	Mon Aug 13 10:32:22 2007 +0200
@@ -34,6 +34,7 @@
 
 #include <windows.h>
 #include <shellapi.h>
+#include <signal.h>
 
 /* Implemenation-specific data. Pointed to by Lisp_Process->process_data */
 struct nt_process_data
@@ -54,7 +55,315 @@
 {
   return (NT_DATA (p)->h_process);
 }
+
+/*-----------------------------------------------------------------------*/
+/* Running remote threads. See Microsoft Systems Journal 1994 Number 5	 */
+/* Jeffrey Richter, Load Your 32-bit DLL into Another Process's Address..*/
+/*-----------------------------------------------------------------------*/
 
+typedef struct
+{
+  HANDLE h_process;
+  HANDLE h_thread;
+  LPVOID address;
+} process_memory;
+
+/*
+ * Allocate SIZE bytes in H_PROCESS address space. Fill in PMC used
+ * further by other routines. Return nonzero if successful.
+ *
+ * The memory in other process is allocated by creating a suspended
+ * thread. Initial stack of that thread is used as the memory
+ * block. The thread entry point is the routine ExitThread in
+ * kernel32.dll, so the allocated memory is freed just by resuming the 
+ * thread, which immediately terminates after that.
+ */
+
+static int 
+alloc_process_memory (HANDLE h_process, size_t size,
+		      process_memory* pmc)
+{
+  LPTHREAD_START_ROUTINE adr_ExitThread =
+    (LPTHREAD_START_ROUTINE)
+    GetProcAddress (GetModuleHandle ("kernel32"), "ExitThread");
+  DWORD dw_unused;
+  CONTEXT context;
+  MEMORY_BASIC_INFORMATION mbi;
+
+  pmc->h_process = h_process;
+  pmc->h_thread = CreateRemoteThread (h_process, NULL, size,
+				     adr_ExitThread, NULL,
+				     CREATE_SUSPENDED, &dw_unused);
+  if (pmc->h_thread == NULL)
+    return 0;
+
+  /* Get context, for thread's stack pointer */
+  context.ContextFlags = CONTEXT_CONTROL;
+  if (!GetThreadContext (pmc->h_thread, &context))
+    goto failure;
+
+  /* Determine base address of the committed range */
+  if (sizeof(mbi) != VirtualQueryEx (h_process,
+#if defined (_X86_)
+				     (LPDWORD)context.Esp - 1,
+#elif defined (_ALPHA_)
+				     (LPDWORD)context.IntSp - 1,
+#else
+#error Unknown processor architecture
+#endif
+				     &mbi, sizeof(mbi)))
+    goto failure;
+
+  /* Change the page protection of the allocated memory to executable,
+     read, and write. */
+  if (!VirtualProtectEx (h_process, mbi.BaseAddress, size,
+			 PAGE_EXECUTE_READWRITE, &dw_unused))
+    goto failure;
+
+  pmc->address = mbi.BaseAddress;
+  return 1;
+
+ failure:
+  ResumeThread (pmc->h_thread);
+  pmc->address = 0;
+  return 0;
+}
+
+static void
+free_process_memory (process_memory* pmc)
+{
+  ResumeThread (pmc->h_thread);
+}
+
+/*
+ * Run ROUTINE in the context of process determined by H_PROCESS. The
+ * routine is passed the address of DATA as parameter. CODE_END is the 
+ * address immediately after ROUTINE's code. DATA_SIZE is the size of
+ * DATA structure.
+ *
+ * Note that the code must be positionally independent, and compiled
+ * without stack checks (they cause implicit calls into CRT so will
+ * fail). DATA should not refer any data in calling process, as both
+ * routine and its data are copied into remote process. Size of data
+ * and code together should not exceed one page (4K on x86 systems).
+ *
+ * Return the value returned by ROUTINE, or (DWORD)-1 if call failed.
+ */
+static DWORD
+run_in_other_process (HANDLE h_process,
+		      LPTHREAD_START_ROUTINE routine, LPVOID code_end,
+		      LPVOID data, size_t data_size)
+{
+  process_memory pm;
+  size_t code_size = (LPBYTE)code_end - (LPBYTE)routine;
+  /* Need at most 3 extra bytes of memory, for data alignment */
+  size_t total_size = code_size + data_size + 3;
+  LPVOID remote_data;
+  HANDLE h_thread;
+  DWORD dw_unused;
+
+  /* Allocate memory */
+  if (!alloc_process_memory (h_process, total_size, &pm))
+    return (DWORD)-1;
+
+  /* Copy code */
+  if (!WriteProcessMemory (h_process, pm.address, (LPVOID)routine,
+			   code_size, NULL))
+    goto failure;
+
+  /* Copy data */
+  if (data_size)
+    {
+      remote_data = (LPBYTE)pm.address + ((code_size + 4) & ~3);
+      if (!WriteProcessMemory (h_process, remote_data, data, data_size, NULL))
+	goto failure;
+    }
+  else
+    remote_data = NULL;
+
+  /* Execute the remote copy of code, passing it remote data */
+  h_thread = CreateRemoteThread (h_process, NULL, 0,
+				 (LPTHREAD_START_ROUTINE) pm.address,
+				 remote_data, 0, &dw_unused);
+  if (h_thread == NULL)
+    goto failure;
+
+  /* Wait till thread finishes */
+  WaitForSingleObject (h_thread, INFINITE);
+
+  /* Free remote memory */
+  free_process_memory (&pm);
+
+  /* Return thread's exit code */
+  {
+    DWORD exit_code;
+    GetExitCodeThread (h_thread, &exit_code);
+    CloseHandle (h_thread);
+    return exit_code;
+  }
+
+ failure:
+  free_process_memory (&pm);
+  return (DWORD)-1;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Sending signals							 */
+/*-----------------------------------------------------------------------*/
+
+/*
+ * We handle the following signals:
+ *
+ * SIGKILL, SIGTERM, SIGQUIT - These three translate to ExitProcess
+ *    executed by the remote process
+ * SIGINT - The remote process is sent CTRL_BREAK_EVENT
+ */
+
+/*
+ * Sending SIGKILL
+ */
+typedef struct
+{
+  void (WINAPI *adr_ExitProcess) (UINT);
+} sigkill_data;
+
+static DWORD WINAPI
+sigkill_proc (sigkill_data* data)
+{
+  (*data->adr_ExitProcess)(255);
+  return 1;
+}
+
+/* Watermark in code space */
+static void
+sigkill_code_end (void)
+{
+}
+
+/*
+ * Sending break or control c
+ */
+typedef struct
+{
+  BOOL (WINAPI *adr_GenerateConsoleCtrlEvent) (DWORD, DWORD);
+  DWORD event;
+} sigint_data;
+
+static DWORD WINAPI
+sigint_proc (sigint_data* data)
+{
+  return (*data->adr_GenerateConsoleCtrlEvent) (data->event, 0);
+}
+
+/* Watermark in code space */
+static void
+sigint_code_end (void)
+{
+}
+
+/*
+ * Enabling signals
+ */
+typedef struct
+{
+  BOOL (WINAPI *adr_SetConsoleCtrlHandler) (LPVOID, BOOL);
+} sig_enable_data;
+
+static DWORD WINAPI
+sig_enable_proc (sig_enable_data* data)
+{
+  (*data->adr_SetConsoleCtrlHandler) (NULL, FALSE);
+  return 1;
+}
+
+/* Watermark in code space */
+static void
+sig_enable_code_end (void)
+{
+}
+
+/*
+ * Send signal SIGNO to process H_PROCESS.
+ * Return nonzero if successful.
+ */
+
+/* This code assigns a return value of GetProcAddress to function pointers
+   of many different types. Instead of heavy obscure casts, we just disable
+   warnings about assignments to different function pointer types. */
+#pragma warning (disable : 4113)
+
+static int
+send_signal (HANDLE h_process, int signo)
+{
+  HMODULE h_kernel = GetModuleHandle ("kernel32");
+  DWORD retval;
+  
+  assert (h_kernel != NULL);
+  
+  switch (signo)
+    {
+    case SIGKILL:
+    case SIGTERM:
+    case SIGQUIT:
+      {
+	sigkill_data d;
+	d.adr_ExitProcess = GetProcAddress (h_kernel, "ExitProcess");
+	assert (d.adr_ExitProcess);
+	retval = run_in_other_process (h_process,
+				       sigkill_proc, sigkill_code_end,
+				       &d, sizeof (d));
+	break;
+      }
+    case SIGINT:
+      {
+	sigint_data d;
+	d.adr_GenerateConsoleCtrlEvent =
+	  GetProcAddress (h_kernel, "GenerateConsoleCtrlEvent");
+	assert (d.adr_GenerateConsoleCtrlEvent);
+	d.event = CTRL_C_EVENT;
+	retval = run_in_other_process (h_process,
+				       sigint_proc, sigint_code_end,
+				       &d, sizeof (d));
+	break;
+      }
+    default:
+      assert (0);
+    }
+
+  return (int)retval > 0 ? 1 : 0;
+}
+
+/*
+ * Enable CTRL_C_EVENT handling in a new child process
+ */
+static void
+enable_child_signals (HANDLE h_process)
+{
+  HMODULE h_kernel = GetModuleHandle ("kernel32");
+  sig_enable_data d;
+  
+  assert (h_kernel != NULL);
+  d.adr_SetConsoleCtrlHandler =
+    GetProcAddress (h_kernel, "SetConsoleCtrlHandler");
+  assert (d.adr_SetConsoleCtrlHandler);
+  run_in_other_process (h_process,
+			sig_enable_proc, sig_enable_code_end,
+			&d, sizeof (d));
+}
+  
+#pragma warning (default : 4113)
+
+/*
+ * Signal error if SIGNO is not supported
+ */
+static void
+validate_signal_number (int signo)
+{
+  if (signo != SIGKILL && signo != SIGTERM
+      && signo != SIGQUIT && signo != SIGINT)
+    error ("Signal number %d not supported", signo);
+}
+  
 /*-----------------------------------------------------------------------*/
 /* Process methods							 */
 /*-----------------------------------------------------------------------*/
@@ -226,7 +535,8 @@
       }
 
     err = (CreateProcess (NULL, command_line, NULL, NULL, TRUE,
-			  CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
+			  CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP
+			  | CREATE_SUSPENDED,
 			  NULL, current_dir, &si, &pi)
 	   ? 0 : GetLastError ());
 
@@ -261,6 +571,10 @@
 	CloseHandle (pi.hProcess);
       }
 
+    if (!windowed)
+      enable_child_signals (pi.hProcess);
+
+    ResumeThread (pi.hThread);
     CloseHandle (pi.hThread);
 
     /* Hack to support Windows 95 negative pids */
@@ -357,6 +671,67 @@
   Lstream_flush (XLSTREAM (DATA_OUTSTREAM(p)));
 }
 
+/*
+ * Send a signal number SIGNO to PROCESS.
+ * CURRENT_GROUP means send to the process group that currently owns
+ * the terminal being used to communicate with PROCESS.
+ * This is used for various commands in shell mode.
+ * If NOMSG is zero, insert signal-announcements into process's buffers
+ * right away.
+ *
+ * If we can, we try to signal PROCESS by sending control characters
+ * down the pty.  This allows us to signal inferiors who have changed
+ * their uid, for which killpg would return an EPERM error.
+ *
+ * The method signals an error if the given SIGNO is not valid
+ */
+
+static void
+nt_kill_child_process (Lisp_Object proc, int signo,
+		       int current_group, int nomsg)
+{
+  struct Lisp_Process *p = XPROCESS (proc);
+
+  /* Signal error if SIGNO cannot be sent */
+  validate_signal_number (signo);
+
+  /* Send signal */
+  if (!send_signal (NT_DATA(p)->h_process, signo))
+    error ("Cannot send signal to process");
+}
+
+/*
+ * Kill any process in the system given its PID.
+ *
+ * Returns zero if a signal successfully sent, or
+ * negative number upon failure
+ */
+static int
+nt_kill_process_by_pid (int pid, int signo)
+{
+  HANDLE h_process;
+  int send_result;
+  
+  /* Signal error if SIGNO cannot be sent */
+  validate_signal_number (signo);
+
+  /* Try to open the process with required privileges */
+  h_process = OpenProcess (PROCESS_CREATE_THREAD
+			   | PROCESS_QUERY_INFORMATION 
+			   | PROCESS_VM_OPERATION
+			   | PROCESS_VM_WRITE,
+			   FALSE, pid);
+  if (h_process == NULL)
+    return -1;
+  
+  send_result = send_signal (h_process, signo);
+  
+  CloseHandle (h_process);
+
+  return send_result ? 0 : -1;
+}
+
+
 /*-----------------------------------------------------------------------*/
 /* Initialization							 */
 /*-----------------------------------------------------------------------*/
@@ -372,8 +747,8 @@
   PROCESS_HAS_METHOD (nt, create_process);
   PROCESS_HAS_METHOD (nt, update_status_if_terminated);
   PROCESS_HAS_METHOD (nt, send_process);
-  /* PROCESS_HAS_METHOD (nt, kill_child_process); */
-  /* PROCESS_HAS_METHOD (nt, kill_process_by_pid); */
+  PROCESS_HAS_METHOD (nt, kill_child_process);
+  PROCESS_HAS_METHOD (nt, kill_process_by_pid);
 #if 0 /* Yet todo */
 #ifdef HAVE_SOCKETS
   PROCESS_HAS_METHOD (nt, canonicalize_host_name);