listen socket passing support for ARM
authorChristian Grothoff <christian@grothoff.org>
Wed, 23 Jun 2010 14:23:50 +0000 (14:23 +0000)
committerChristian Grothoff <christian@grothoff.org>
Wed, 23 Jun 2010 14:23:50 +0000 (14:23 +0000)
ChangeLog
src/arm/arm_api.c
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/util/os_priority.c

index 861d8bd25cb41fbe9e60c244c59728712ae62c8f..2761b94d9bb8bb74235d166c298a579a821acd64 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+Wed Jun 23 16:34:38 CEST 2010
+       Added support for systemd-compatible passing of listen-sockets
+       by ARM to services as well as systemd compatibility for gnunet-service-arm
+       itself.  At least for non-MINGW systems this should work.
+
 Sat Jun  5 18:08:39 CEST 2010
        Added support for UNIX domain sockets, code also defaults to
        them when available.
index 083031e7866fba6f310d3fbf3adcd70c50799ce3..6c2b53a715c7c7cae2ca3474319bfa228421eea3 100644 (file)
@@ -434,7 +434,8 @@ arm_service_report (void *cls,
       GNUNET_free (lopostfix);
       return;
     }
-  pid = do_start_process (loprefix,
+  pid = do_start_process (NULL,
+                         loprefix,
                          binary,
                          "-c", config,
 #if DEBUG_ARM
index 357d399209a1b553608b760bf4a2f815a2af1276..d616ac2b72dd8dcb95afc4dc06ffc4852556be05 100644 (file)
@@ -7,12 +7,14 @@
  * limitation that it does NOT allow passing command line arguments
  * with spaces to the new process.
  *
+ * @param lsocks array of listen sockets to dup starting at fd3 (systemd-style), or NULL
  * @param first_arg first argument for argv (may be an empty string)
  * @param ... more arguments, NULL terminated
  * @return PID of the started process, -1 on error
  */
 static pid_t
-do_start_process (const char *first_arg, ...)
+do_start_process (const int *lsocks,
+                 const char *first_arg, ...)
 {
   va_list ap;
   char **argv;
@@ -86,7 +88,7 @@ do_start_process (const char *first_arg, ...)
   while (NULL != (arg = (va_arg (ap, const char*))));
   va_end (ap);
   argv[argv_size] = NULL;
-  pid = GNUNET_OS_start_process_v (argv[0], argv);
+  pid = GNUNET_OS_start_process_v (lsocks, argv[0], argv);
   while (argv_size > 0)
     GNUNET_free (argv[--argv_size]);
   GNUNET_free (argv);
index ab96906a720d4925a73df53013ee5b22e236c01d..59f48a58c2ceca89a76985b390597d1f1712ba4d 100644 (file)
@@ -344,7 +344,8 @@ free_entry (struct ServiceList *pos)
  * @param sl identifies service to start
  */
 static void
-start_process (struct ServiceList *sl)
+start_process (struct ServiceList *sl,
+              const int *lsocks)
 {
   char *loprefix;
   char *options;
@@ -415,14 +416,16 @@ start_process (struct ServiceList *sl)
              sl->name, sl->binary, sl->config);
 #endif
   if (GNUNET_YES == use_debug)
-    sl->pid = do_start_process (loprefix,
+    sl->pid = do_start_process (lsocks,
+                               loprefix,                               
                                sl->binary,
                                "-c", sl->config,
                                "-L", "DEBUG",
                                options,
                                NULL);
   else
-    sl->pid = do_start_process (loprefix,
+    sl->pid = do_start_process (lsocks,
+                               loprefix,
                                sl->binary,
                                "-c", sl->config,
                                options,
@@ -442,9 +445,13 @@ start_process (struct ServiceList *sl)
  *
  * @param client who is asking for this
  * @param servicename name of the service to start
+ * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL
+ * @return GNUNET_OK on success, GNUNET_SYSERR on error
  */
-void
-start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
+int
+start_service (struct GNUNET_SERVER_Client *client, 
+              const char *servicename,
+              const int *lsocks)
 {
   struct ServiceList *sl;
   char *binary;
@@ -457,7 +464,7 @@ start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
                  _("ARM is shutting down, service `%s' not started.\n"),
                  servicename);
       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
-      return;
+      return GNUNET_SYSERR;
     }
   sl = find_name (servicename);
   if (sl != NULL)
@@ -467,7 +474,7 @@ start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
       sl->next = running;
       running = sl;
       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
-      return;
+      return GNUNET_SYSERR;
     }
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -477,7 +484,7 @@ start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
                  _("Binary implementing service `%s' not known!\n"),
                  servicename);
       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
-      return;
+      return GNUNET_SYSERR;
     }
   if ((GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_filename (cfg,
@@ -492,7 +499,7 @@ start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
       GNUNET_free (binary);
       GNUNET_free_non_null (config);
-      return;
+      return GNUNET_SYSERR;
     }
   (void) stop_listening (servicename);
   sl = GNUNET_malloc (sizeof (struct ServiceList));
@@ -505,9 +512,10 @@ start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
   sl->restartAt = GNUNET_TIME_UNIT_FOREVER_ABS;
 
   running = sl;
-  start_process (sl);
+  start_process (sl, lsocks);
   if (NULL != client)
     signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
+  return GNUNET_OK;
 }
 
 
@@ -610,7 +618,7 @@ handle_start (void *cls,
       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
       return;
     }
-  start_service (client, servicename);
+  start_service (client, servicename, NULL);
   GNUNET_SERVER_receive_done (client, GNUNET_OK);
 }
 
@@ -776,7 +784,7 @@ delayed_restart_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
            {
              GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                          _("Restarting service `%s'.\n"), pos->name);
-             start_process (pos);
+             start_process (pos, NULL);
            }
          else
            {
@@ -1071,7 +1079,7 @@ run (void *cls,
          pos = strtok (defaultservices, " ");
          while (pos != NULL)
            {
-             start_service (NULL, pos);
+             start_service (NULL, pos, NULL);
              pos = strtok (NULL, " ");
            }
        }
index 77dfb8c6c04a30308710a3f9900b0644c0f87286..632b46b099cfd065abb9499567a6abc7468372a9 100644 (file)
 #ifndef GNUNET_SERVICE_ARM__H
 #define GNUNET_SERVICE_ARM__H
 
-void start_service (struct GNUNET_SERVER_Client *client,
-                   const char *servicename);
+/**
+ * Start the specified service.
+ *
+ * @param client who is asking for this
+ * @param servicename name of the service to start 
+ * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL
+ * @return GNUNET_OK on success
+ */
+int start_service (struct GNUNET_SERVER_Client *client,
+                  const char *servicename,
+                  const int *lsocks);
 
 /**
  * Stop listening for connections to a service.
index 9380a8debfe3d3c68c0c7611aa3147c0d5d6f053..bc8db396b454d52e09efea533d7f205e4ae2bff5 100644 (file)
@@ -847,7 +847,6 @@ stop_listening (const char *serviceName)
   return ret;
 }
 
-
 /**
  * First connection has come to the listening socket associated with the service,
  * create the service in order to relay the incoming connection to it
@@ -856,14 +855,15 @@ stop_listening (const char *serviceName)
  * @param tc context 
  */
 static void
-acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+
+#if MINGW 
+static void
+accept_and_forward (struct ServiceListeningInfo *serviceListeningInfo)
 {
-  struct ServiceListeningInfo *serviceListeningInfo = cls;
   struct ForwardedConnection *fc;
 
-  serviceListeningInfo->acceptTask = GNUNET_SCHEDULER_NO_TASK;
-  if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason))
-    return;
   fc = GNUNET_malloc (sizeof (struct ForwardedConnection));
   fc->listen_info = serviceListeningInfo;
   fc->service_to_client_bufferPos = fc->service_to_client_buffer;
@@ -879,6 +879,9 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
                  serviceListeningInfo->serviceName,
                  STRERROR (errno));
       GNUNET_free (fc);
+      GNUNET_CONTAINER_DLL_insert (serviceListeningInfoList_head,
+                                  serviceListeningInfoList_tail, 
+                                  serviceListeningInfo); 
       serviceListeningInfo->acceptTask =
        GNUNET_SCHEDULER_add_read_net (scheduler,
                                       GNUNET_TIME_UNIT_FOREVER_REL, 
@@ -889,10 +892,7 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
     }
   GNUNET_break (GNUNET_OK ==
                GNUNET_NETWORK_socket_close (serviceListeningInfo->listeningSocket));
-  GNUNET_CONTAINER_DLL_remove (serviceListeningInfoList_head,
-                              serviceListeningInfoList_tail, 
-                              serviceListeningInfo);
-  start_service (NULL, serviceListeningInfo->serviceName);
+  start_service (NULL, serviceListeningInfo->serviceName, NULL);
   GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
              _("Service `%s' started\n"),
              fc->listen_info->serviceName);
@@ -909,6 +909,72 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
                                &start_forwarding,
                                fc);
 }
+#endif
+
+
+/**
+ * First connection has come to the listening socket associated with the service,
+ * create the service in order to relay the incoming connection to it
+ * 
+ * @param cls callback data, struct ServiceListeningInfo describing a listen socket
+ * @param tc context 
+ */
+static void
+acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct ServiceListeningInfo *sli = cls;
+  struct ServiceListeningInfo *pos;
+  struct ServiceListeningInfo *next;
+  int *lsocks;
+  unsigned int ls;
+
+  sli->acceptTask = GNUNET_SCHEDULER_NO_TASK;
+  if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason))
+    return;
+  GNUNET_CONTAINER_DLL_remove (serviceListeningInfoList_head,
+                              serviceListeningInfoList_tail, 
+                              sli);  
+#ifndef MINGW
+  lsocks = NULL;
+  ls = 0;
+  next = serviceListeningInfoList_head;
+  while (NULL != (pos = next))
+    {
+      next = pos->next;
+      if (0 == strcmp (pos->serviceName,
+                      sli->serviceName))
+       {
+         GNUNET_array_append (lsocks, ls, 
+                              GNUNET_NETWORK_get_fd (pos->listeningSocket));     
+         GNUNET_free (pos->listeningSocket); /* deliberately no closing! */
+         GNUNET_free (pos->service_addr);
+         GNUNET_free (pos->serviceName);
+         GNUNET_SCHEDULER_cancel (scheduler,
+                                  pos->acceptTask);
+         GNUNET_CONTAINER_DLL_remove (serviceListeningInfoList_head,
+                                      serviceListeningInfoList_tail, 
+                                      pos);
+         GNUNET_free (pos);
+       }
+    }
+  GNUNET_array_append (lsocks, ls, 
+                      GNUNET_NETWORK_get_fd (sli->listeningSocket));
+  GNUNET_free (sli->listeningSocket); /* deliberately no closing! */
+  GNUNET_free (sli->service_addr);
+  GNUNET_array_append (lsocks, ls, -1);
+  start_service (NULL, 
+                sli->serviceName,
+                lsocks);
+  ls = 0;
+  while (lsocks[ls] != -1)
+    GNUNET_break (0 == close (lsocks[ls++]));      
+  GNUNET_array_grow (lsocks, ls, 0);
+  GNUNET_free (sli->serviceName);
+  GNUNET_free (sli); 
+#else
+  accept_and_forward (sli);  
+#endif
+}
 
 
 /**
index d7d3acbf0a2275fa52cdc74c1d5a7d976b18dc1c..19fd234ee78d61941c272140b4065a62828c58d5 100644 (file)
@@ -199,6 +199,7 @@ int GNUNET_NETWORK_socket_select (struct GNUNET_NETWORK_FDSet *rfds,
 ssize_t GNUNET_NETWORK_socket_send (const struct GNUNET_NETWORK_Handle *desc,
                                     const void *buffer, size_t length);
 
+
 /**
  * Send data to a particular destination (always non-blocking).
  * This function only works for UDP sockets.
index 3aa1f1bad68e4963011629e9bf68ae423656e847..87d9a8fb1700a438f704b4360a50bee306c3b2bb 100644 (file)
@@ -216,12 +216,15 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin,
 /**
  * Start a process.
  *
+ * @param lsocks array of listen sockets to dup systemd-style (or NULL);
+ *         must be NULL on platforms where dup is not supported
  * @param filename name of the binary
  * @param argv NULL-terminated list of arguments to the process,
  *             including the process name as the first argument
  * @return process ID of the new process, -1 on error
  */
-pid_t GNUNET_OS_start_process_v (const char *filename, char *const argv[]);
+pid_t GNUNET_OS_start_process_v (const int *lsocks,
+                                const char *filename, char *const argv[]);
 
 
 /**
index b354ad9950a337e5bf1e7e225eddf78b19c9f8f3..7a6f0d65d1ae0b2c585f58daf52d750ed81b8325 100644 (file)
@@ -329,16 +329,43 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin,
 /**
  * Start a process.
  *
+ * @param lsocks array of listen sockets to dup systemd-style (or NULL);
+ *         must be NULL on platforms where dup is not supported
  * @param filename name of the binary
  * @param argv NULL-terminated list of arguments to the process
  * @return process ID of the new process, -1 on error
  */
 pid_t
-GNUNET_OS_start_process_v (const char *filename, char *const argv[])
+GNUNET_OS_start_process_v (const int *lsocks,
+                          const char *filename, char *const argv[])
 {
 #ifndef MINGW
   pid_t ret;
-
+  char lpid[16];
+  char fds[16];
+  int i;
+  int j;
+  int k;
+  int tgt;
+  int flags;
+  int *lscp;
+  unsigned int ls;    
+
+  lscp = NULL;
+  ls = 0;
+  if (lsocks != NULL)
+    {
+      i = 0;
+      while (-1 != (k = lsocks[i++]))
+       {
+         flags = fcntl (k, F_GETFD);
+         GNUNET_assert (flags >= 0);
+         flags &= ~FD_CLOEXEC;
+         (void) fcntl (k, F_SETFD, flags);
+         GNUNET_array_append (lscp, ls, k);
+       }
+      GNUNET_array_append (lscp, ls, -1);
+    }
 #if HAVE_WORKING_VFORK
   ret = vfork ();
 #else
@@ -366,6 +393,48 @@ GNUNET_OS_start_process_v (const char *filename, char *const argv[])
         }
       return ret;
     }
+  if (lscp != NULL)
+    {
+      /* read systemd documentation... */
+      GNUNET_snprintf (lpid, sizeof (lpid), "%u", getpid());
+      setenv ("LISTEN_PID", lpid, 1);      
+      i = 0;
+      tgt = 3;
+      while (-1 != lscp[i])
+       {
+         j = i + 1;
+         while (-1 != lscp[j])
+           {
+             if (lscp[j] == tgt)
+               {
+                 /* dup away */
+                 k = dup (lscp[j]);
+                 GNUNET_assert (-1 != k);
+                 GNUNET_assert (0 == close (lscp[j]));
+                 lscp[j] = k;
+                 break;
+               }
+             j++;
+           }
+         if (lscp[i] != tgt)
+           {
+             /* Bury any existing FD, no matter what; they should all be closed
+                on exec anyway and the important onces have been dup'ed away */
+             (void) close (tgt);             
+             GNUNET_assert (-1 != dup2 (lscp[i], tgt));
+           }
+         /* set close-on-exec flag */
+         flags = fcntl (tgt, F_GETFD);
+         GNUNET_assert (flags >= 0);
+         flags &= ~FD_CLOEXEC;
+         (void) fcntl (tgt, F_SETFD, flags);
+         tgt++;
+         i++;
+       }
+      GNUNET_snprintf (fds, sizeof (fds), "%u", i);
+      setenv ("LISTEN_FDS", fds, 1); 
+    }
+  GNUNET_array_grow (lscp, ls, 0);
   execvp (filename, argv);
   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "execvp", filename);
   _exit (1);
@@ -379,6 +448,7 @@ GNUNET_OS_start_process_v (const char *filename, char *const argv[])
   char *non_const_filename = NULL;
   int filenamelen = 0;
 
+  GNUNET_assert (lsocks == NULL);
   /* Count the number of arguments */
   arg = argv;
   while (*arg)