Implement passing sockets in IPC on W32 (#1975)
authorChristian Grothoff <christian@grothoff.org>
Tue, 6 Dec 2011 17:58:40 +0000 (17:58 +0000)
committerChristian Grothoff <christian@grothoff.org>
Tue, 6 Dec 2011 17:58:40 +0000 (17:58 +0000)
src/arm/do_start_process.c
src/arm/gnunet-service-arm.c
src/arm/gnunet-service-arm.h
src/arm/gnunet-service-arm_interceptor.c
src/include/gnunet_network_lib.h
src/include/gnunet_os_lib.h
src/include/platform.h
src/util/network.c
src/util/os_priority.c
src/util/service.c

index 19b3bc224e9736033e437de157e7b43e3766cc9a..f4d3424ff179ad7010433953ed26bcb394eeff06 100644 (file)
@@ -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;
index 718d902807a44a1104650d4a283c06239c812a9a..d99020d843cd51c5aa2f77bc5912eccdb206e052 100644 (file)
@@ -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;
index 689f26c82b5d3996c4c8ae432384a9bfd1e566fb..8b17ec8164e6619f93d7b7a399696218a8ed7a2a 100644 (file)
@@ -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.
index c5a5fa56bbbc1b9e65358417a80b896842d12fd7..b4032a1531bba384d7a4f449b93eb847410f8414 100644 (file)
@@ -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);
index 65b2042d54bcb76cca10d3cf10851d8ced63704f..a14d5f0bb6fd0d87c2d3287071dfe3a88c2e6084 100644 (file)
@@ -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
index aed5e3b0137ac9080594bed6e1eeaa5262f94dae..ff324e18ab71901e4df6987a10f83c2752e981d3 100644 (file)
@@ -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[]);
 
 
index 0aa59b72f9e22a8e416f7cdbc6d628e93f06012c..1795037313fdc292728f68bf4197b4551b762d63 100644 (file)
@@ -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
index c4861ecfadaea299fbc49784db7ebe979c82b1a0..d8c64276bc8f42d75f878a116a7101a58a7951ac 100644 (file)
@@ -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));
index 9ffa190fa6224eac461f68fd321b944e60b5a84b..de3a5757e04bdf4fc7712147dd95a7d4cc89139f 100644 (file)
@@ -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
 }
index 91fc460e2d1a1dcd7ebf2dd22ad67d447f7cb01d..d37fe87c21ecae4d5ee96f59d8b3e5aec114efbc 100644 (file)
@@ -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) &&