From d234059375e77fe46c57e98751dc8491dff46e85 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 6 Dec 2011 17:58:40 +0000 Subject: [PATCH] Implement passing sockets in IPC on W32 (#1975) --- src/arm/do_start_process.c | 2 +- src/arm/gnunet-service-arm.c | 4 +- src/arm/gnunet-service-arm.h | 2 +- src/arm/gnunet-service-arm_interceptor.c | 12 +- src/include/gnunet_network_lib.h | 5 +- src/include/gnunet_os_lib.h | 2 +- src/include/platform.h | 8 ++ src/util/network.c | 15 ++- src/util/os_priority.c | 133 +++++++++++++++++++++-- src/util/service.c | 89 +++++++++++++++ 10 files changed, 250 insertions(+), 22 deletions(-) diff --git a/src/arm/do_start_process.c b/src/arm/do_start_process.c index 19b3bc224..f4d3424ff 100644 --- a/src/arm/do_start_process.c +++ b/src/arm/do_start_process.c @@ -13,7 +13,7 @@ * @return PID of the started process, -1 on error */ static struct GNUNET_OS_Process * -do_start_process (const int *lsocks, const char *first_arg, ...) +do_start_process (const SOCKTYPE *lsocks, const char *first_arg, ...) { va_list ap; char **argv; diff --git a/src/arm/gnunet-service-arm.c b/src/arm/gnunet-service-arm.c index 718d90280..d99020d84 100644 --- a/src/arm/gnunet-service-arm.c +++ b/src/arm/gnunet-service-arm.c @@ -331,7 +331,7 @@ free_service (struct ServiceList *pos) * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL */ static void -start_process (struct ServiceList *sl, const int *lsocks) +start_process (struct ServiceList *sl, const SOCKTYPE *lsocks) { char *loprefix; char *options; @@ -422,7 +422,7 @@ start_process (struct ServiceList *sl, const int *lsocks) */ int start_service (struct GNUNET_SERVER_Client *client, const char *servicename, - const int *lsocks) + const SOCKTYPE *lsocks) { struct ServiceList *sl; char *binary; diff --git a/src/arm/gnunet-service-arm.h b/src/arm/gnunet-service-arm.h index 689f26c82..8b17ec816 100644 --- a/src/arm/gnunet-service-arm.h +++ b/src/arm/gnunet-service-arm.h @@ -37,7 +37,7 @@ */ int start_service (struct GNUNET_SERVER_Client *client, const char *servicename, - const int *lsocks); + const SOCKTYPE *lsocks); /** * Stop listening for connections to a service. diff --git a/src/arm/gnunet-service-arm_interceptor.c b/src/arm/gnunet-service-arm_interceptor.c index c5a5fa56b..b4032a153 100644 --- a/src/arm/gnunet-service-arm_interceptor.c +++ b/src/arm/gnunet-service-arm_interceptor.c @@ -980,9 +980,9 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) struct ServiceListeningInfo *sli = cls; struct ServiceListeningInfo *pos; struct ServiceListeningInfo *next; - int *lsocks; + SOCKTYPE *lsocks; unsigned int ls; - int use_lsocks; + int disable_lsocks; sli->acceptTask = GNUNET_SCHEDULER_NO_TASK; if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) @@ -1025,11 +1025,19 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) GNUNET_NETWORK_get_fd (sli->listeningSocket)); GNUNET_free (sli->listeningSocket); /* deliberately no closing! */ GNUNET_free (sli->service_addr); +#if WINDOWS + GNUNET_array_append (lsocks, ls, INVALID_SOCKET); +#else GNUNET_array_append (lsocks, ls, -1); +#endif start_service (NULL, sli->serviceName, lsocks); ls = 0; while (lsocks[ls] != -1) +#if WINDOWS + GNUNET_break (0 == closesocket (lsocks[ls++])); +#else GNUNET_break (0 == close (lsocks[ls++])); +#endif GNUNET_array_grow (lsocks, ls, 0); GNUNET_free (sli->serviceName); GNUNET_free (sli); diff --git a/src/include/gnunet_network_lib.h b/src/include/gnunet_network_lib.h index 65b2042d5..a14d5f0bb 100644 --- a/src/include/gnunet_network_lib.h +++ b/src/include/gnunet_network_lib.h @@ -96,7 +96,7 @@ GNUNET_NETWORK_socket_accept (const struct GNUNET_NETWORK_Handle *desc, * @return NULL on error (including not supported on target platform) */ struct GNUNET_NETWORK_Handle * -GNUNET_NETWORK_socket_box_native (int fd); +GNUNET_NETWORK_socket_box_native (SOCKTYPE fd); /** @@ -320,8 +320,7 @@ GNUNET_NETWORK_fdset_set (struct GNUNET_NETWORK_FDSet *fds, const struct GNUNET_NETWORK_Handle *desc); -#ifdef __MINGW32__ -/* TODO: maybe #ifdef WINDOWS? -ndurner */ +#if WINDOWS /** * Add a W32 file handle to the fd set * @param fds fd set diff --git a/src/include/gnunet_os_lib.h b/src/include/gnunet_os_lib.h index aed5e3b01..ff324e18a 100644 --- a/src/include/gnunet_os_lib.h +++ b/src/include/gnunet_os_lib.h @@ -275,7 +275,7 @@ GNUNET_OS_start_process_va (struct GNUNET_DISK_PipeHandle *pipe_stdin, * @return pointer to process structure of the new process, NULL on error */ struct GNUNET_OS_Process * -GNUNET_OS_start_process_v (const int *lsocks, const char *filename, +GNUNET_OS_start_process_v (const SOCKTYPE *lsocks, const char *filename, char *const argv[]); diff --git a/src/include/platform.h b/src/include/platform.h index 0aa59b72f..179503731 100644 --- a/src/include/platform.h +++ b/src/include/platform.h @@ -246,4 +246,12 @@ atoll (const char *nptr); #define MAKE_UNALIGNED(val) val #endif +#if WINDOWS +#define FDTYPE HANDLE +#define SOCKTYPE SOCKET +#else +#define FDTYPE int +#define SOCKTYPE int +#endif + #endif diff --git a/src/util/network.c b/src/util/network.c index c4861ecfa..d8c64276b 100644 --- a/src/util/network.c +++ b/src/util/network.c @@ -377,13 +377,20 @@ GNUNET_NETWORK_socket_close (struct GNUNET_NETWORK_Handle *desc) * @return NULL on error (including not supported on target platform) */ struct GNUNET_NETWORK_Handle * -GNUNET_NETWORK_socket_box_native (int fd) +GNUNET_NETWORK_socket_box_native (SOCKTYPE fd) { + struct GNUNET_NETWORK_Handle *ret; #if MINGW - return NULL; + unsigned long i; + DWORD d; + /* FIXME: Find a better call to check that FD is valid */ + if (WSAIoctl (fd, FIONBIO, (void *) &i, sizeof (i), NULL, 0, &d, NULL, NULL) != 0) + return NULL; /* invalid FD */ + ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); + ret->fd = fd; + ret->af = AF_UNSPEC; + return ret; #else - struct GNUNET_NETWORK_Handle *ret; - if (fcntl (fd, F_GETFD) < 0) return NULL; /* invalid FD */ ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); diff --git a/src/util/os_priority.c b/src/util/os_priority.c index 9ffa190fa..de3a5757e 100644 --- a/src/util/os_priority.c +++ b/src/util/os_priority.c @@ -852,7 +852,8 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, * @return process ID of the new process, -1 on error */ struct GNUNET_OS_Process * -GNUNET_OS_start_process_v (const int *lsocks, const char *filename, +GNUNET_OS_start_process_v (const SOCKTYPE *lsocks, + const char *filename, char *const argv[]) { #if ENABLE_WINDOWS_WORKAROUNDS @@ -979,7 +980,7 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, char path[MAX_PATH + 1]; - char *our_env[3] = { NULL, NULL, NULL }; + char *our_env[5] = { NULL, NULL, NULL, NULL, NULL }; char *env_block = NULL; char *pathbuf; DWORD pathbuf_len, alloc_len; @@ -988,8 +989,12 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, char *libdir; char *ptr; char *non_const_filename; + struct GNUNET_DISK_PipeHandle *lsocks_pipe; + const struct GNUNET_DISK_FileHandle *lsocks_write_fd; + HANDLE lsocks_read; + HANDLE lsocks_write; - GNUNET_assert (lsocks == NULL); + int fail; /* Search in prefix dir (hopefully - the directory from which * the current module was loaded), bindir and libdir, then in PATH @@ -1103,6 +1108,25 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, GNUNET_free (path); return NULL; } + if (lsocks != NULL) + { + lsocks_pipe = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO); + + if (lsocks_pipe == NULL) + { + GNUNET_free (cmd); + GNUNET_free (path); + GNUNET_DISK_pipe_close (lsocks_pipe); + return NULL; + } + lsocks_write_fd = GNUNET_DISK_pipe_handle (lsocks_pipe, + GNUNET_DISK_PIPE_END_WRITE); + GNUNET_DISK_internal_file_handle_ (lsocks_write_fd, + &lsocks_write, sizeof (HANDLE)); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle + (lsocks_pipe, GNUNET_DISK_PIPE_END_READ), + &lsocks_read, sizeof (HANDLE)); + } #if DEBUG_OS LOG (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", @@ -1111,17 +1135,31 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE); GNUNET_asprintf (&our_env[1], "%s", childpipename); - our_env[2] = NULL; + GNUNET_free (childpipename); + if (lsocks == NULL) + our_env[2] = NULL; + else + { + /*This will tell the child that we're going to send lsocks over the pipe*/ + GNUNET_asprintf (&our_env[2], "%s=", "GNUNET_OS_READ_LSOCKS"); + GNUNET_asprintf (&our_env[3], "%lu", lsocks_read); + our_env[4] = NULL; + } env_block = CreateCustomEnvTable (our_env); - GNUNET_free (our_env[0]); - GNUNET_free (our_env[1]); + GNUNET_free_non_null (our_env[0]); + GNUNET_free_non_null (our_env[1]); + GNUNET_free_non_null (our_env[2]); + GNUNET_free_non_null (our_env[3]); - if (!CreateProcess - (path, cmd, NULL, NULL, FALSE, DETACHED_PROCESS | CREATE_SUSPENDED, + if (!CreateProcessA + (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS | CREATE_SUSPENDED, env_block, NULL, &start, &proc)) { SetErrnoFromWinError (GetLastError ()); LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "CreateProcess"); + GNUNET_DISK_npipe_close (control_pipe); + if (lsocks != NULL) + GNUNET_DISK_pipe_close (lsocks_pipe); GNUNET_free (env_block); GNUNET_free (cmd); return NULL; @@ -1140,6 +1178,85 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, CloseHandle (proc.hThread); GNUNET_free (cmd); + if (lsocks == NULL) + return gnunet_proc; + + GNUNET_DISK_pipe_close_end (lsocks_pipe, GNUNET_DISK_PIPE_END_READ); + + /* This is a replacement for "goto error" that doesn't use goto */ + fail = 1; + do + { + int wrote; + uint64_t size, count, i; + + /* Tell the number of sockets */ + for (count = 0; lsocks && lsocks[count] != INVALID_SOCKET; count++); + + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &count, sizeof (count)); + if (wrote != sizeof (count)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u count bytes to the child: %u\n", sizeof (count), GetLastError ()); + break; + } + for (i = 0; lsocks && lsocks[i] != INVALID_SOCKET; i++) + { + WSAPROTOCOL_INFOA pi; + /* Get a socket duplication info */ + if (SOCKET_ERROR == WSADuplicateSocketA (lsocks[i], gnunet_proc->pid, &pi)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to duplicate an socket[%llu]: %u\n", i, GetLastError ()); + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "CreateProcess"); + break; + } + /* Synchronous I/O is not nice, but we can't schedule this: + * lsocks will be closed/freed by the caller soon, and until + * the child creates a duplicate, closing a socket here will + * close it for good. + */ + /* Send the size of the structure + * (the child might be built with different headers...) + */ + size = sizeof (pi); + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &size, sizeof (size)); + if (wrote != sizeof (size)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u size[%llu] bytes to the child: %u\n", sizeof (size), i, GetLastError ()); + break; + } + /* Finally! Send the data */ + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &pi, sizeof (pi)); + if (wrote != sizeof (pi)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u socket[%llu] bytes to the child: %u\n", sizeof (pi), i, GetLastError ()); + break; + } + } + /* This will block us until the child makes a final read or closes + * the pipe (hence no 'wrote' check), since we have to wait for it + * to duplicate the last socket, before we return and start closing + * our own copies) + */ + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &count, sizeof (count)); + fail = 0; + } + while (fail); + + GNUNET_DISK_file_sync (lsocks_write_fd); + GNUNET_DISK_pipe_close (lsocks_pipe); + + if (fail) + { + /* If we can't pass on the socket(s), the child will block forever, + * better put it out of its misery. + */ + TerminateProcess (gnunet_proc->handle, 0); + CloseHandle (gnunet_proc->handle); + GNUNET_DISK_npipe_close (gnunet_proc->control_pipe); + GNUNET_free (gnunet_proc); + return NULL; + } + return gnunet_proc; #endif } diff --git a/src/util/service.c b/src/util/service.c index 91fc460e2..d37fe87c2 100644 --- a/src/util/service.c +++ b/src/util/service.c @@ -1079,6 +1079,89 @@ GNUNET_SERVICE_get_server_addresses (const char *serviceName, } +#ifdef MINGW +/** + * @return GNUNET_YES if ok, GNUNET_NO if not ok (must bind yourself), + * and GNUNET_SYSERR on error. + */ +static int +receive_sockets_from_parent (struct GNUNET_SERVICE_Context *sctx) +{ + const char *env_buf; + int fail; + uint64_t count, i; + HANDLE lsocks_pipe; + + env_buf = getenv ("GNUNET_OS_READ_LSOCKS"); + if ((env_buf == NULL) || (strlen (env_buf) <= 0)) + { + return GNUNET_NO; + } + /* Using W32 API directly here, because this pipe will + * never be used outside of this function, and it's just too much of a bother + * to create a GNUnet API that boxes a HANDLE (the way it is done with socks) + */ + lsocks_pipe = (HANDLE) strtoul (env_buf, NULL, 10); + if (lsocks_pipe == 0 || lsocks_pipe == INVALID_HANDLE_VALUE) + return GNUNET_NO; + + fail = 1; + do + { + int ret; + int fail2; + DWORD rd; + + ret = ReadFile (lsocks_pipe, &count, sizeof (count), &rd, NULL); + if (ret == 0 || rd != sizeof (count) || count == 0) + break; + sctx->lsocks = + GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle *) * (count + 1)); + + fail2 = 1; + for (i = 0; i < count; i++) + { + WSAPROTOCOL_INFOA pi; + uint64_t size; + SOCKET s; + ret = ReadFile (lsocks_pipe, &size, sizeof (size), &rd, NULL); + if (ret == 0 || rd != sizeof (size) || size != sizeof (pi)) + break; + ret = ReadFile (lsocks_pipe, &pi, sizeof (pi), &rd, NULL); + if (ret == 0 || rd != sizeof (pi)) + break; + s = WSASocketA (pi.iAddressFamily, pi.iSocketType, pi.iProtocol, &pi, 0, WSA_FLAG_OVERLAPPED); + sctx->lsocks[i] = GNUNET_NETWORK_socket_box_native (s); + if (sctx->lsocks[i] == NULL) + break; + else if (i == count - 1) + fail2 = 0; + } + if (fail2) + break; + sctx->lsocks[count] = NULL; + fail = 0; + } + while (fail); + + CloseHandle (lsocks_pipe); + + if (fail) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Could not access a pre-bound socket, will try to bind myself\n")); + for (i = 0; sctx->lsocks[i] != NULL && i < count; i++) + GNUNET_break (0 == GNUNET_NETWORK_socket_close (sctx->lsocks[i])); + GNUNET_free (sctx->lsocks); + sctx->lsocks = NULL; + return GNUNET_NO; + } + + return GNUNET_YES; +} +#endif + + /** * Setup addr, addrlen, idle_timeout * based on configuration! @@ -1175,6 +1258,12 @@ setup_service (struct GNUNET_SERVICE_Context *sctx) unsetenv ("LISTEN_PID"); unsetenv ("LISTEN_FDS"); } +#else + if (getenv ("GNUNET_OS_READ_LSOCKS") != NULL) + { + receive_sockets_from_parent (sctx); + putenv ("GNUNET_OS_READ_LSOCKS="); + } #endif if ((sctx->lsocks == NULL) && -- 2.25.1