From edbe7000c8c3c77e4e67b2e4b17a71133a579194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Wed, 10 Nov 2010 12:59:14 +0300 Subject: [PATCH 1/2] [W32] Parent->child control pipe * GNUNET_OS_start_process* () will create a pipe that is inheritable by the child and place its HANDLE into environment variable table to be passed to the child * GNUNET_SCHEDULER_run () will call a function that will read this variable and start a thread to handle data coming from the pipe * GNUNET_OS_process_kill () will write control codes into the pipe, wait for the child to die, and use TerminateProcess() on it in case the wait times out * Extra functions to create GNUNET_DISK_PIPE from existing native handles * Extra functions (W32-only) for working with environment tables --- src/include/gnunet_os_lib.h | 12 ++ src/util/disk.c | 90 +++++++++++++ src/util/disk.h | 2 + src/util/network.c | 16 ++- src/util/os_priority.c | 297 +++++++++++++++++++++++++++++++++++++----- src/util/scheduler.c | 2 + 6 files changed, 380 insertions(+), 39 deletions(-) diff --git a/src/include/gnunet_os_lib.h b/src/include/gnunet_os_lib.h index 5ff2b48..6bc6035 100644 --- a/src/include/gnunet_os_lib.h +++ b/src/include/gnunet_os_lib.h @@ -273,6 +273,18 @@ int GNUNET_OS_process_status (struct GNUNET_OS_Process *proc, */ int GNUNET_OS_process_wait (struct GNUNET_OS_Process *proc); +/** + * Read env variable and set up the read end of the control + * pipe that parent uses to send signals to this process. + * + * Only does something on Windows + * + * @param cls user-specified parameter (NULL) + * @param tc task context + */ +void GNUNET_OS_install_parent_control_handler (void *cls, + const struct + GNUNET_SCHEDULER_TaskContext * tc); #if 0 /* keep Emacsens' auto-indent happy */ { diff --git a/src/util/disk.c b/src/util/disk.c index 307c453..1a3b79c 100644 --- a/src/util/disk.c +++ b/src/util/disk.c @@ -1910,4 +1910,94 @@ GNUNET_DISK_internal_file_handle_ (const struct GNUNET_DISK_FileHandle *fh, return GNUNET_OK; } +struct GNUNET_DISK_FileHandle +GNUNET_DISK_file_from_internal_handle_ (void *src, size_t src_len) +{ + struct GNUNET_DISK_FileHandle ret; +#ifdef MINGW + if (src_len == sizeof (HANDLE)) + ret.h = *(HANDLE *) src; + else + ret.h = INVALID_HANDLE_VALUE; +#else + if (src_len == sizeof (int)) + ret.fd = *(int *) src; + else + ret.fd = -1; +#endif + return ret; +} + +struct GNUNET_DISK_PipeHandle * +GNUNET_DISK_pipe_from_internal_handles_ (int blocking, void *src0, void *src1, size_t src_len) +{ + struct GNUNET_DISK_PipeHandle *p; + struct GNUNET_DISK_FileHandle *fds; + + p = + GNUNET_malloc (sizeof (struct GNUNET_DISK_PipeHandle) + + 2 * sizeof (struct GNUNET_DISK_FileHandle)); + fds = (struct GNUNET_DISK_FileHandle *) &p[1]; + p->fd[0] = &fds[0]; + p->fd[1] = &fds[1]; + fds[0] = GNUNET_DISK_file_from_internal_handle_ (src0, src_len); + fds[1] = GNUNET_DISK_file_from_internal_handle_ (src1, src_len); +#ifndef MINGW + int fd[2]; + int ret; + int flags; + int eno; + + fd[0] = fds[0].fd; + fd[1] = fds[1].fd; + ret = 0; + if (fd[0] >= 0) + { + flags = fcntl (fd[0], F_GETFL); + if (!blocking) + flags |= O_NONBLOCK; + if (0 > fcntl (fd[0], F_SETFL, flags)) + ret = -1; + flags = fcntl (fd[0], F_GETFD); + flags |= FD_CLOEXEC; + if (0 > fcntl (fd[0], F_SETFD, flags)) + ret = -1; + } + if (fd[1] >= 0) + { + flags = fcntl (fd[1], F_GETFL); + if (!blocking) + flags |= O_NONBLOCK; + if (0 > fcntl (fd[1], F_SETFL, flags)) + ret = -1; + flags = fcntl (fd[1], F_GETFD); + flags |= FD_CLOEXEC; + if (0 > fcntl (fd[1], F_SETFD, flags)) + ret = -1; + } + if (ret == -1) + { + eno = errno; + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fcntl"); + GNUNET_free (p); + errno = eno; + return NULL; + } +#else + /* Empirical fact: DuplicateHandle() on a handle inherited from the parent + * dies with ERROR_INVALID_HANDLE, so we won't try to duplicate these to + * make them inheritable/uninheritable. + */ + if (!blocking) + { + DWORD mode; + + mode = PIPE_NOWAIT; + SetNamedPipeHandleState (p->fd[0]->h, &mode, NULL, NULL); + SetNamedPipeHandleState (p->fd[1]->h, &mode, NULL, NULL); + /* this always fails on Windows 95, so we don't care about error handling */ + } +#endif + return p; +} /* end of disk.c */ diff --git a/src/util/disk.h b/src/util/disk.h index 3d52b9e..5e9647e 100644 --- a/src/util/disk.h +++ b/src/util/disk.h @@ -61,5 +61,7 @@ struct GNUNET_DISK_FileHandle */ int GNUNET_DISK_internal_file_handle_ (const struct GNUNET_DISK_FileHandle *fh, void *dst, size_t dst_len); + +struct GNUNET_DISK_PipeHandle *GNUNET_DISK_pipe_from_internal_handles_ (int blocking, void *src0, void *src1, size_t src_len); #endif /* GNUNET_DISK_H_ */ diff --git a/src/util/network.c b/src/util/network.c index 58d67ed..78ff01e 100644 --- a/src/util/network.c +++ b/src/util/network.c @@ -1138,9 +1138,19 @@ GNUNET_NETWORK_socket_select (struct GNUNET_NETWORK_FDSet *rfds, { HANDLE h; - DWORD dwBytes; + DWORD dwBytes = 0, error_code; + BOOL bresult; h = *(HANDLE *) GNUNET_CONTAINER_slist_get (i, NULL); - if (!PeekNamedPipe (h, NULL, 0, NULL, &dwBytes, NULL)) + bresult = PeekNamedPipe (h, NULL, 0, NULL, &dwBytes, NULL); + error_code = GetLastError (); + /* FIXME: This should fail when the pipe is _really_ invalid, + * should skip the pipe if the error means that it's + * temporarily unavailable, and should succeed if the pipe + * is broken (or that the otherwise valid pipe is in a condition + * that prevents further reading). + * For now it succeeds only when the pipe is broken or readable. + */ + if (!bresult && error_code != ERROR_BROKEN_PIPE) { retcode = -1; SetErrnoFromWinError (GetLastError ()); @@ -1152,7 +1162,7 @@ GNUNET_NETWORK_socket_select (struct GNUNET_NETWORK_FDSet *rfds, #endif goto select_loop_end; } - else if (dwBytes) + else if (dwBytes || error_code == ERROR_BROKEN_PIPE) { GNUNET_CONTAINER_slist_add (handles_read, diff --git a/src/util/os_priority.c b/src/util/os_priority.c index fb6c292..10beff4 100644 --- a/src/util/os_priority.c +++ b/src/util/os_priority.c @@ -29,11 +29,14 @@ #include "gnunet_os_lib.h" #include "disk.h" +#define GNUNET_OS_NT_CONTROL_PIPE "GNUNET_OS_NT_CONTROL_PIPE" + struct GNUNET_OS_Process { pid_t pid; #if WINDOWS HANDLE handle; + struct GNUNET_DISK_PipeHandle *control_pipe; #endif }; @@ -42,14 +45,81 @@ static struct GNUNET_OS_Process current_process; #if WINDOWS void -GNUNET_OS_process_set_handle(struct GNUNET_OS_Process *proc, HANDLE handle) +GNUNET_OS_parent_control_handler (void *cls, + const struct + GNUNET_SCHEDULER_TaskContext * tc) { - if (proc->handle != NULL) - CloseHandle (proc->handle); - proc->handle = handle; + struct GNUNET_DISK_PipeHandle *control_pipe = (struct GNUNET_DISK_PipeHandle *) cls; + unsigned char code; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "GNUNET_OS_parent_control_handler is invoked because of %d\n", tc->reason); + + if (tc->reason & (GNUNET_SCHEDULER_REASON_SHUTDOWN | GNUNET_SCHEDULER_REASON_TIMEOUT | GNUNET_SCHEDULER_REASON_PREREQ_DONE)) + { + GNUNET_DISK_pipe_close (control_pipe); + } + else + { + if (GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_READ), &code, sizeof (char)) != sizeof (char)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "GNUNET_DISK_file_read have failed, errno = %d. Closing the control pipe.\n", errno); + GNUNET_DISK_pipe_close (control_pipe); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got control code %d\n", code); + switch (code) + { + case 0: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Raising SIGTERM\n"); + raise (SIGTERM); + break; + case 1: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Raising SIGKILL\n"); + raise (SIGKILL); + break; + case 255: + /* read more bytes - a possibility for extended signals + * in case 254 different valies is not enough + */ + break; + default: + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Re-adding parent control handler pipe to the scheduler\n"); + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_READ), GNUNET_OS_parent_control_handler, control_pipe); + } + } } #endif +void +GNUNET_OS_install_parent_control_handler (void *cls, + const struct + GNUNET_SCHEDULER_TaskContext * tc) +{ +#ifdef WINDOWS + char *env_buf; + char *endptr; + HANDLE control_pipe_end[2]; + struct GNUNET_DISK_PipeHandle *control_pipe = NULL; + + env_buf = getenv (GNUNET_OS_NT_CONTROL_PIPE); + if (env_buf == NULL || strlen (env_buf) <= 0 || env_buf[0] < '0' || env_buf[0] > '9') + return; + + control_pipe_end[0] = (HANDLE) strtol (env_buf, &endptr, 10); + control_pipe_end[1] = INVALID_HANDLE_VALUE; + + control_pipe = GNUNET_DISK_pipe_from_internal_handles_ (GNUNET_NO, &control_pipe_end[0], &control_pipe_end[1], sizeof (HANDLE)); + if (control_pipe == NULL) + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding parent control handler pipe to the scheduler\n"); + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_READ), GNUNET_OS_parent_control_handler, control_pipe); +#endif +} + /** * Get process structure for current process @@ -66,7 +136,7 @@ GNUNET_OS_process_current () current_process.pid = GetCurrentProcessId (); current_process.handle = GetCurrentProcess (); #else - current_process.pid = 0; + current_process.pid = getpid (); #endif return ¤t_process; } @@ -75,27 +145,56 @@ int GNUNET_OS_process_kill (struct GNUNET_OS_Process *proc, int sig) { #if WINDOWS - if (sig == SIGKILL || sig == SIGTERM) + unsigned char c; + DWORD dwresult, error_code = NO_ERROR; + BOOL bresult = 1; + + switch (sig) { - HANDLE h = proc->handle; - if (NULL == h) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Invalid process information {%d, %08X}\n"), - proc->pid, - h); - return -1; - } - if (!TerminateProcess (h, 0)) - { - SetErrnoFromWinError (GetLastError ()); - return -1; + case SIGKILL: + c = 1; + break; + case SIGTERM: + c = 0; + break; + default: + errno = EINVAL; + return -1; + } + + if (GNUNET_DISK_file_write ( + GNUNET_DISK_pipe_handle (proc->control_pipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c)) != sizeof (c)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Failed to write into control pipe, errno is %d\n", errno); + bresult = 0; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Wrote control code into control pipe, now waiting\n"); + SetLastError (0); + dwresult = WaitForSingleObject (proc->handle, 4*1000); + error_code = GetLastError (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Woke up, returned %d, GLE is %d\n", dwresult, error_code); + if (WAIT_OBJECT_0 != dwresult || error_code != NO_ERROR) + { + SetLastError (0); + bresult = TerminateProcess (proc->handle, 0); + error_code = GetLastError (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "TerminateProcess for the child returned %d, GLE is %d\n", bresult, + error_code); } - else - return 0; } - errno = EINVAL; - return -1; + if (error_code != NO_ERROR || !bresult) + { + return -1; + } + else + return 0; #else return kill (proc->pid, sig); #endif @@ -258,6 +357,80 @@ GNUNET_OS_set_process_priority (struct GNUNET_OS_Process *proc, return GNUNET_OK; } +#if WINDOWS +/** + * Look through the @env_table and find its size (in bytes, excluding + * all NULL-terminators) and item count. + * + * Use count + size + 1 to calculate the size of the envtable in memory + * + * @param env_table environment table, as returned by GetEnvironmentStrings() + * @param count returned number of elements + * @param size returned total length of the items + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +int +GetEnvTableSizeA (const char *env_table, size_t *count, size_t *size) +{ + size_t tablesize = 0; + size_t items_count = 0; + const char *ptr; + if (!env_table) + return GNUNET_SYSERR; + for (items_count = 0, ptr = win32_env_table; ptr[0] != 0; items_count++) + { + size_t len = strlen (ptr); + tablesize += len; + ptr += len + 1; + } + if (count) + *count = items_count; + if (size) + *size = tablesize; + return GNUNET_OK; +} + +char * +CreateEnvTableA () +{ + char *result = NULL; + size_t count, size; + const char *win32_env_table; + win32_env_table = GetEnvironmentStrings (); + if (win32_env_table == NULL || GNUNET_OK != GetEnvTableSizeA (win32_env_table, &count, &size)) + return NULL; + result = GNUNET_malloc (count + size + 1); + if (result) + memcpy (result, win32_env_table, count + size + 1); + if (win32_env_table) + FreeEnvironmentStrings (win32_env_table); + return result; +} + +char * +AddToEnvTableA (char *env_table, const char *newvar) +{ + char *result = NULL, *ptr; + size_t count, size, new_var_size; + new_var_size = strlen (newvar); + if (env_table == NULL || GNUNET_OK != GetEnvTableSizeA ((const char *) env_table, &count, &size)) + { + GNUNET_free (env_table); + return NULL; + } + result = GNUNET_malloc (count + 1 + size + new_var_size + 1); + if (result) + { + memcpy (result, win32_env_table, count + size); + ptr = result + count + size; + memcpy (ptr, newvar, new_var_size + 1); + ptr[new_var_size + 1] = 0; + } + GNUNET_free (env_table); + return result; +} +#endif + /** * Start a process. * @@ -368,19 +541,31 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, STARTUPINFO start; PROCESS_INFORMATION proc; struct GNUNET_OS_Process *gnunet_proc = NULL; + struct GNUNET_DISK_PipeHandle *control_pipe = NULL; + char *env_buf; + char *env_block = NULL; HANDLE stdin_handle; HANDLE stdout_handle; + HANDLE control_pipe_read_end, control_pipe_write_end; char path[MAX_PATH + 1]; - cmdlen = 0; + if ((HINSTANCE) 32 >= FindExecutableA (filename, NULL, path)) + { + SetErrnoFromWinError (GetLastError ()); + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "FindExecutable", filename); + return NULL; + } + + cmdlen = strlen (path) + 3; va_start (ap, filename); while (NULL != (arg = va_arg (ap, char *))) cmdlen = cmdlen + strlen (arg) + 3; va_end (ap); cmd = idx = GNUNET_malloc (sizeof (char) * (cmdlen + 1)); + idx += sprintf (idx, "\"%s\" ", path); va_start (ap, filename); while (NULL != (arg = va_arg (ap, char *))) idx += sprintf (idx, "\"%s\" ", arg); @@ -404,35 +589,47 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, start.hStdOutput = stdout_handle; } - if (32 >= (int) FindExecutableA (filename, NULL, path)) - { - SetErrnoFromWinError (GetLastError ()); - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "FindExecutable", filename); - return NULL; - } + control_pipe = GNUNET_DISK_pipe (GNUNET_NO, 1, 0); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_READ), &control_pipe_read_end, sizeof (HANDLE)); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_WRITE), &control_pipe_write_end, sizeof (HANDLE)); + + env_block = CreateEnvTableA (env_block); + GNUNET_asprintf (&env_buf, "%s=%d", GNUNET_OS_NT_CONTROL_PIPE, control_pipe_read_end); + env_block = AddToEnvTableA (env_block, (const char *) env_buf); + GNUNET_free (env_buf); if (!CreateProcessA - (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &start, + (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS, env_block, NULL, &start, &proc)) { SetErrnoFromWinError (GetLastError ()); GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "CreateProcess", path); - return NULL; + goto end; } gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process)); gnunet_proc->pid = proc.dwProcessId; gnunet_proc->handle = proc.hProcess; + gnunet_proc->control_pipe = control_pipe; CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL); CloseHandle (proc.hThread); +end: + + if (env_block) + GNUNET_free (env_block); + + if (gnunet_proc == NULL) + GNUNET_DISK_pipe_close (control_pipe); + else + GNUNET_DISK_pipe_close_end (control_pipe, GNUNET_DISK_PIPE_END_READ); + GNUNET_free (cmd); return gnunet_proc; #endif - } @@ -557,6 +754,9 @@ GNUNET_OS_start_process_v (const int *lsocks, int argcount = 0; char non_const_filename[MAX_PATH +1]; struct GNUNET_OS_Process *gnunet_proc = NULL; + struct GNUNET_DISK_PipeHandle *control_pipe = NULL; + char *env_buf = NULL, *env_block = NULL; + HANDLE control_pipe_read_end, control_pipe_write_end; GNUNET_assert (lsocks == NULL); @@ -583,7 +783,12 @@ GNUNET_OS_start_process_v (const int *lsocks, arg = (char **) argv; while (*arg) { - non_const_argv[argcount] = GNUNET_strdup (*arg); + if (argcount == 0) + /* Otherwise we'll get the argv[0] without .exe extension, + that will break libtool wrapper */ + non_const_argv[argcount] = GNUNET_strdup (non_const_filename); + else + non_const_argv[argcount] = GNUNET_strdup (*arg); arg++; argcount++; } @@ -610,22 +815,42 @@ GNUNET_OS_start_process_v (const int *lsocks, memset (&start, 0, sizeof (start)); start.cb = sizeof (start); - if (!CreateProcess - (non_const_filename, cmd, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &start, + control_pipe = GNUNET_DISK_pipe (GNUNET_NO, 1, 0); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_READ), &control_pipe_read_end, sizeof (HANDLE)); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle (control_pipe, GNUNET_DISK_PIPE_END_WRITE), &control_pipe_write_end, sizeof (HANDLE)); + + env_block = CreateEnvTableA (env_block); + GNUNET_asprintf (&env_buf, "%s=%d", GNUNET_OS_NT_CONTROL_PIPE, control_pipe_read_end); + env_block = AddToEnvTableA (env_block, (const char *) env_buf); + GNUNET_free (env_buf); + + if (!CreateProcessA + (non_const_filename, cmd, NULL, NULL, TRUE, + DETACHED_PROCESS, env_block, NULL, &start, &proc)) { SetErrnoFromWinError (GetLastError ()); GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "CreateProcess"); - return NULL; + goto end; } gnunet_proc = GNUNET_malloc (sizeof (struct GNUNET_OS_Process)); gnunet_proc->pid = proc.dwProcessId; gnunet_proc->handle = proc.hProcess; + gnunet_proc->control_pipe = control_pipe; CreateThread (NULL, 64000, ChildWaitThread, (void *) gnunet_proc, 0, NULL); CloseHandle (proc.hThread); +end: + if (env_block) + GNUNET_free (env_block); + + if (gnunet_proc == NULL) + GNUNET_DISK_pipe_close (control_pipe); + else + GNUNET_DISK_pipe_close_end (control_pipe, GNUNET_DISK_PIPE_END_READ); + GNUNET_free (cmd); while (argcount > 0) diff --git a/src/util/scheduler.c b/src/util/scheduler.c index 0a4798b..88275e9 100644 --- a/src/util/scheduler.c +++ b/src/util/scheduler.c @@ -744,6 +744,8 @@ GNUNET_SCHEDULER_run (GNUNET_SCHEDULER_Task task, void *task_cls) shc_hup = GNUNET_SIGNAL_handler_install (SIGHUP, &sighandler_shutdown); #endif current_priority = GNUNET_SCHEDULER_PRIORITY_DEFAULT; + GNUNET_SCHEDULER_add_continuation (GNUNET_OS_install_parent_control_handler, + NULL, GNUNET_SCHEDULER_REASON_STARTUP); GNUNET_SCHEDULER_add_continuation (task, task_cls, GNUNET_SCHEDULER_REASON_STARTUP); -- 1.7.3.1.msysgit.0