Merge branch 'master' of ssh://git.gnunet.org/gnunet
[oweals/gnunet.git] / src / arm / arm_api.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2010, 2012, 2013, 2016, 2019 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file arm/arm_api.c
23  * @brief API for accessing the ARM service
24  * @author Christian Grothoff
25  * @author LRN
26  */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_arm_service.h"
30 #include "gnunet_protocols.h"
31 #include "arm.h"
32
33 #define LOG(kind, ...) GNUNET_log_from (kind, "arm-api", __VA_ARGS__)
34
35
36 /**
37  * Entry in a doubly-linked list of operations awaiting for replies
38  * (in-order) from the ARM service.
39  */
40 struct GNUNET_ARM_Operation
41 {
42   /**
43    * This is a doubly-linked list.
44    */
45   struct GNUNET_ARM_Operation *next;
46
47   /**
48    * This is a doubly-linked list.
49    */
50   struct GNUNET_ARM_Operation *prev;
51
52   /**
53    * ARM handle.
54    */
55   struct GNUNET_ARM_Handle *h;
56
57   /**
58    * Callback for service state change requests.
59    */
60   GNUNET_ARM_ResultCallback result_cont;
61
62   /**
63    * Callback for service list requests.
64    */
65   GNUNET_ARM_ServiceListCallback list_cont;
66
67   /**
68    * Closure for @e result_cont or @e list_cont.
69    */
70   void *cont_cls;
71
72   /**
73    * Task for async completion.
74    */
75   struct GNUNET_SCHEDULER_Task *async;
76
77   /**
78    * Unique ID for the request.
79    */
80   uint64_t id;
81
82   /**
83    * Result of this operation for #notify_starting().
84    */
85   enum GNUNET_ARM_Result starting_ret;
86
87   /**
88    * File descriptor to close on operation stop, if not NULL.
89    */
90   struct GNUNET_DISK_FileHandle *rfd;
91
92   /**
93    * Is this an operation to stop the ARM service?
94    */
95   int is_arm_stop;
96 };
97
98
99 /**
100  * Handle for interacting with ARM.
101  */
102 struct GNUNET_ARM_Handle
103 {
104   /**
105    * Our connection to the ARM service.
106    */
107   struct GNUNET_MQ_Handle *mq;
108
109   /**
110    * The configuration that we are using.
111    */
112   const struct GNUNET_CONFIGURATION_Handle *cfg;
113
114   /**
115    * Head of doubly-linked list of pending operations.
116    */
117   struct GNUNET_ARM_Operation *operation_pending_head;
118
119   /**
120    * Tail of doubly-linked list of pending operations.
121    */
122   struct GNUNET_ARM_Operation *operation_pending_tail;
123
124   /**
125    * Callback to invoke on connection/disconnection.
126    */
127   GNUNET_ARM_ConnectionStatusCallback conn_status;
128
129   /**
130    * Closure for @e conn_status.
131    */
132   void *conn_status_cls;
133
134   /**
135    * ARM operation where the goal is to wait for ARM shutdown to
136    * complete.  This operation is special in that it waits for an
137    * error on the @e mq.  So we complete it by calling the
138    * continuation in the #mq_error_handler().  Note that the operation
139    * is no longer in the @e operation_pending_head DLL once it is
140    * referenced from this field.
141    */
142   struct GNUNET_ARM_Operation *thm;
143
144   /**
145    * ID of the reconnect task (if any).
146    */
147   struct GNUNET_SCHEDULER_Task *reconnect_task;
148
149   /**
150    * Current delay we use for re-trying to connect to core.
151    */
152   struct GNUNET_TIME_Relative retry_backoff;
153
154   /**
155    * Counter for request identifiers.  They are used to match replies
156    * from ARM to operations in the @e operation_pending_head DLL.
157    */
158   uint64_t request_id_counter;
159
160   /**
161    * Have we detected that ARM is up?
162    */
163   int currently_up;
164 };
165
166
167 /**
168  * Connect to arm.
169  *
170  * @param h arm handle
171  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
172  */
173 static int
174 reconnect_arm (struct GNUNET_ARM_Handle *h);
175
176
177 /**
178  * Task scheduled to try to re-connect to arm.
179  *
180  * @param cls the `struct GNUNET_ARM_Handle`
181  */
182 static void
183 reconnect_arm_task (void *cls)
184 {
185   struct GNUNET_ARM_Handle *h = cls;
186
187   h->reconnect_task = NULL;
188   reconnect_arm (h);
189 }
190
191
192 /**
193  * Close down any existing connection to the ARM service and
194  * try re-establishing it later.
195  *
196  * @param h our handle
197  */
198 static void
199 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
200 {
201   struct GNUNET_ARM_Operation *op;
202
203   if (NULL != h->mq)
204   {
205     GNUNET_MQ_destroy (h->mq);
206     h->mq = NULL;
207   }
208   h->currently_up = GNUNET_NO;
209   GNUNET_assert (NULL == h->reconnect_task);
210   h->reconnect_task =
211     GNUNET_SCHEDULER_add_delayed (h->retry_backoff,
212                                   &reconnect_arm_task,
213                                   h);
214   while (NULL != (op = h->operation_pending_head))
215   {
216     if (NULL != op->result_cont)
217       op->result_cont (op->cont_cls,
218                        GNUNET_ARM_REQUEST_DISCONNECTED,
219                        0);
220     if (NULL != op->list_cont)
221       op->list_cont (op->cont_cls,
222                      GNUNET_ARM_REQUEST_DISCONNECTED,
223                      0,
224                      NULL);
225     GNUNET_ARM_operation_cancel (op);
226   }
227   GNUNET_assert (NULL == h->operation_pending_head);
228   h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
229   if (NULL != h->conn_status)
230     h->conn_status (h->conn_status_cls,
231                     GNUNET_NO);
232 }
233
234
235 /**
236  * Find a control message by its unique ID.
237  *
238  * @param h ARM handle
239  * @param id unique message ID to use for the lookup
240  * @return NULL if not found
241  */
242 static struct GNUNET_ARM_Operation *
243 find_op_by_id (struct GNUNET_ARM_Handle *h,
244                uint64_t id)
245 {
246   for (struct GNUNET_ARM_Operation *result = h->operation_pending_head;
247        NULL != result;
248        result = result->next)
249     if (id == result->id)
250       return result;
251   return NULL;
252 }
253
254
255 /**
256  * Handler for ARM replies.
257  *
258  * @param cls our `struct GNUNET_ARM_Handle`
259  * @param res the message received from the arm service
260  */
261 static void
262 handle_arm_result (void *cls,
263                    const struct GNUNET_ARM_ResultMessage *res)
264 {
265   struct GNUNET_ARM_Handle *h = cls;
266   struct GNUNET_ARM_Operation *op;
267   uint64_t id;
268   enum GNUNET_ARM_Result result;
269   GNUNET_ARM_ResultCallback result_cont;
270   void *result_cont_cls;
271
272   id = GNUNET_ntohll (res->arm_msg.request_id);
273   op = find_op_by_id (h,
274                       id);
275   if (NULL == op)
276   {
277     LOG (GNUNET_ERROR_TYPE_DEBUG,
278          "Message with unknown id %llu\n",
279          (unsigned long long) id);
280     return;
281   }
282
283   result = (enum GNUNET_ARM_Result) ntohl (res->result);
284   if ( (GNUNET_YES == op->is_arm_stop) &&
285        (GNUNET_ARM_RESULT_STOPPING == result) )
286   {
287     /* special case: if we are stopping 'gnunet-service-arm', we do not just
288        wait for the result message, but also wait for the service to close
289        the connection (and then we have to close our client handle as well);
290        this is done by installing a different receive handler, waiting for
291        the connection to go down */
292     if (NULL != h->thm)
293     {
294       GNUNET_break (0);
295       op->result_cont (h->thm->cont_cls,
296                        GNUNET_ARM_REQUEST_SENT_OK,
297                        GNUNET_ARM_RESULT_IS_NOT_KNOWN);
298       GNUNET_free (h->thm);
299     }
300     GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
301                                  h->operation_pending_tail,
302                                  op);
303     h->thm = op;
304     return;
305   }
306   result_cont = op->result_cont;
307   result_cont_cls = op->cont_cls;
308   GNUNET_ARM_operation_cancel (op);
309   if (NULL != result_cont)
310     result_cont (result_cont_cls,
311                  GNUNET_ARM_REQUEST_SENT_OK,
312                  result);
313 }
314
315
316 /**
317  * Read from a string pool.
318  *
319  * @param pool_start start of the string pool
320  * @param pool_size size of the string pool
321  * @param str_index index into the string pool
322  * @returns an index into the string pool, or
323  *          NULL if the index is out of bounds
324  */
325 static const char *
326 pool_get (const char *pool_start,
327           size_t pool_size,
328           size_t str_index)
329 {
330   const char *str_start;
331   const char *end;
332
333   if (str_index >= pool_size)
334     return NULL;
335   str_start = pool_start + str_index;
336   end = memchr (str_start, 0, pool_size - str_index);
337   if (NULL == end)
338     return NULL;
339   return str_start;
340 }
341
342
343 /**
344  * Check that list result message is well-formed.
345  *
346  * @param cls our `struct GNUNET_ARM_Handle`
347  * @param lres the message received from the arm service
348  * @return #GNUNET_OK if message is well-formed
349  */
350 static int
351 check_arm_list_result (void *cls,
352                        const struct GNUNET_ARM_ListResultMessage *lres)
353 {
354   uint16_t rcount = ntohs (lres->count);
355   uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof(*lres);
356   struct GNUNET_ARM_ServiceInfoMessage *ssm;
357   size_t pool_size;
358   char *pool_start;
359
360   (void) cls;
361   if ((rcount * sizeof (struct GNUNET_ARM_ServiceInfoMessage) > msize))
362   {
363     GNUNET_break_op (0);
364     return GNUNET_NO;
365   }
366   ssm = (struct GNUNET_ARM_ServiceInfoMessage *) &lres[1];
367   pool_start = (char *) (ssm + rcount);
368   pool_size = msize - (rcount * sizeof (struct GNUNET_ARM_ServiceInfoMessage));
369   for (unsigned int i = 0; i < rcount; i++)
370   {
371     uint16_t name_index = ntohs (ssm->name_index);
372     uint16_t binary_index = ntohs (ssm->binary_index);
373     if (NULL == pool_get (pool_start,
374                           pool_size,
375                           name_index))
376     {
377       GNUNET_break_op (0);
378       return GNUNET_NO;
379     }
380     if (NULL == pool_get (pool_start,
381                           pool_size,
382                           binary_index))
383     {
384       GNUNET_break_op (0);
385       return GNUNET_NO;
386     }
387     ssm++;
388   }
389   return GNUNET_OK;
390 }
391
392
393 /**
394  * Handler for ARM list replies.
395  *
396  * @param cls our `struct GNUNET_ARM_Handle`
397  * @param lres the message received from the arm service
398  */
399 static void
400 handle_arm_list_result (void *cls,
401                         const struct GNUNET_ARM_ListResultMessage *lres)
402 {
403   struct GNUNET_ARM_Handle *h = cls;
404   uint16_t rcount = ntohs (lres->count);
405   uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof(*lres);
406   struct GNUNET_ARM_ServiceInfo list[rcount];
407   struct GNUNET_ARM_ServiceInfoMessage *ssm;
408   struct GNUNET_ARM_Operation *op;
409   uint64_t id;
410   size_t pool_size;
411   char *pool_start;
412
413   id = GNUNET_ntohll (lres->arm_msg.request_id);
414   op = find_op_by_id (h, id);
415   if (NULL == op)
416   {
417     LOG (GNUNET_ERROR_TYPE_DEBUG,
418          "Message with unknown id %llu\n",
419          (unsigned long long) id);
420     return;
421   }
422
423   GNUNET_assert ((rcount * sizeof (struct GNUNET_ARM_ServiceInfoMessage) <=
424                   msize));
425
426   ssm = (struct GNUNET_ARM_ServiceInfoMessage *) &lres[1];
427   pool_start = (char *) (ssm + rcount);
428   pool_size = msize - (rcount * sizeof (struct GNUNET_ARM_ServiceInfoMessage));
429
430   for (unsigned int i = 0; i < rcount; i++)
431   {
432     uint16_t name_index = ntohs (ssm->name_index);
433     uint16_t binary_index = ntohs (ssm->binary_index);
434     const char *name;
435     const char *binary;
436
437     GNUNET_assert (NULL != (name = pool_get (pool_start,
438                                              pool_size,
439                                              name_index)));
440     GNUNET_assert (NULL != (binary = pool_get (pool_start,
441                                                pool_size,
442                                                binary_index)));
443     list[i] = (struct GNUNET_ARM_ServiceInfo) {
444       .name = name,
445       .binary = binary,
446       .status = ntohl (ssm->status),
447       .last_started_at = GNUNET_TIME_absolute_ntoh (ssm->last_started_at),
448       .restart_at = GNUNET_TIME_absolute_ntoh (ssm->restart_at),
449       .last_exit_status = ntohs (ssm->last_exit_status),
450     };
451     ssm++;
452   }
453   if (NULL != op->list_cont)
454     op->list_cont (op->cont_cls,
455                    GNUNET_ARM_REQUEST_SENT_OK,
456                    rcount,
457                    list);
458   GNUNET_ARM_operation_cancel (op);
459 }
460
461
462 /**
463  * Receive confirmation from test, ARM service is up.
464  *
465  * @param cls closure with the `struct GNUNET_ARM_Handle`
466  * @param msg message received
467  */
468 static void
469 handle_confirm (void *cls,
470                 const struct GNUNET_MessageHeader *msg)
471 {
472   struct GNUNET_ARM_Handle *h = cls;
473
474   (void) msg;
475   LOG (GNUNET_ERROR_TYPE_DEBUG,
476        "Got confirmation from ARM that we are up!\n");
477   if (GNUNET_NO == h->currently_up)
478   {
479     h->currently_up = GNUNET_YES;
480     if (NULL != h->conn_status)
481       h->conn_status (h->conn_status_cls, GNUNET_YES);
482   }
483 }
484
485
486 /**
487  * Generic error handler, called with the appropriate error code and
488  * the same closure specified at the creation of the message queue.
489  * Not every message queue implementation supports an error handler.
490  *
491  * @param cls closure with the `struct GNUNET_ARM_Handle *`
492  * @param error error code
493  */
494 static void
495 mq_error_handler (void *cls,
496                   enum GNUNET_MQ_Error error)
497 {
498   struct GNUNET_ARM_Handle *h = cls;
499   struct GNUNET_ARM_Operation *op;
500
501   (void) error;
502   h->currently_up = GNUNET_NO;
503   if (NULL != (op = h->thm))
504   {
505     h->thm = NULL;
506     op->result_cont (op->cont_cls,
507                      GNUNET_ARM_REQUEST_SENT_OK,
508                      GNUNET_ARM_RESULT_STOPPED);
509     GNUNET_free (op);
510   }
511   reconnect_arm_later (h);
512 }
513
514
515 /**
516  * Connect to arm.
517  *
518  * @param h arm handle
519  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
520  */
521 static int
522 reconnect_arm (struct GNUNET_ARM_Handle *h)
523 {
524   struct GNUNET_MQ_MessageHandler handlers[] = {
525     GNUNET_MQ_hd_fixed_size (arm_result,
526                              GNUNET_MESSAGE_TYPE_ARM_RESULT,
527                              struct GNUNET_ARM_ResultMessage,
528                              h),
529     GNUNET_MQ_hd_var_size (arm_list_result,
530                            GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT,
531                            struct GNUNET_ARM_ListResultMessage,
532                            h),
533     GNUNET_MQ_hd_fixed_size (confirm,
534                              GNUNET_MESSAGE_TYPE_ARM_TEST,
535                              struct GNUNET_MessageHeader,
536                              h),
537     GNUNET_MQ_handler_end ()
538   };
539   struct GNUNET_MessageHeader *test;
540   struct GNUNET_MQ_Envelope *env;
541
542   if (NULL != h->mq)
543     return GNUNET_OK;
544   GNUNET_assert (GNUNET_NO == h->currently_up);
545   h->mq = GNUNET_CLIENT_connect (h->cfg,
546                                  "arm",
547                                  handlers,
548                                  &mq_error_handler,
549                                  h);
550   if (NULL == h->mq)
551   {
552     LOG (GNUNET_ERROR_TYPE_DEBUG,
553          "GNUNET_CLIENT_connect returned NULL\n");
554     if (NULL != h->conn_status)
555       h->conn_status (h->conn_status_cls,
556                       GNUNET_SYSERR);
557     return GNUNET_SYSERR;
558   }
559   LOG (GNUNET_ERROR_TYPE_DEBUG,
560        "Sending TEST message to ARM\n");
561   env = GNUNET_MQ_msg (test,
562                        GNUNET_MESSAGE_TYPE_ARM_TEST);
563   GNUNET_MQ_send (h->mq, env);
564   return GNUNET_OK;
565 }
566
567
568 /**
569  * Set up a context for communicating with ARM, then
570  * start connecting to the ARM service using that context.
571  *
572  * @param cfg configuration to use (needed to contact ARM;
573  *        the ARM service may internally use a different
574  *        configuration to determine how to start the service).
575  * @param conn_status will be called when connecting/disconnecting
576  * @param conn_status_cls closure for @a conn_status
577  * @return context to use for further ARM operations, NULL on error.
578  */
579 struct GNUNET_ARM_Handle *
580 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
581                     GNUNET_ARM_ConnectionStatusCallback conn_status,
582                     void *conn_status_cls)
583 {
584   struct GNUNET_ARM_Handle *h;
585
586   h = GNUNET_new (struct GNUNET_ARM_Handle);
587   h->cfg = cfg;
588   h->conn_status = conn_status;
589   h->conn_status_cls = conn_status_cls;
590   if (GNUNET_OK != reconnect_arm (h))
591   {
592     GNUNET_free (h);
593     return NULL;
594   }
595   return h;
596 }
597
598
599 /**
600  * Disconnect from the ARM service (if connected) and destroy the context.
601  *
602  * @param h the handle that was being used
603  */
604 void
605 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
606 {
607   struct GNUNET_ARM_Operation *op;
608
609   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
610   while (NULL != (op = h->operation_pending_head))
611   {
612     GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
613                                  h->operation_pending_tail,
614                                  op);
615     if (NULL != op->result_cont)
616       op->result_cont (op->cont_cls,
617                        GNUNET_ARM_REQUEST_DISCONNECTED,
618                        0);
619     if (NULL != op->list_cont)
620       op->list_cont (op->cont_cls,
621                      GNUNET_ARM_REQUEST_DISCONNECTED,
622                      0,
623                      NULL);
624     if (NULL != op->async)
625     {
626       GNUNET_SCHEDULER_cancel (op->async);
627       op->async = NULL;
628     }
629     GNUNET_free (op);
630   }
631   if (NULL != h->mq)
632   {
633     GNUNET_MQ_destroy (h->mq);
634     h->mq = NULL;
635   }
636   if (NULL != h->reconnect_task)
637   {
638     GNUNET_SCHEDULER_cancel (h->reconnect_task);
639     h->reconnect_task = NULL;
640   }
641   GNUNET_free (h);
642 }
643
644
645 /**
646  * A client specifically requested starting of ARM itself.
647  * Starts the ARM service.
648  *
649  * @param h the handle with configuration details
650  * @param std_inheritance inheritance of std streams
651  * @param sigfd socket to pass to ARM for signalling
652  * @return operation status code
653  */
654 static enum GNUNET_ARM_Result
655 start_arm_service (struct GNUNET_ARM_Handle *h,
656                    enum GNUNET_OS_InheritStdioFlags std_inheritance,
657                    struct GNUNET_DISK_FileHandle *sigfd)
658 {
659   struct GNUNET_OS_Process *proc;
660   char *cbinary;
661   char *binary;
662   char *quotedbinary;
663   char *config;
664   char *loprefix;
665   char *lopostfix;
666   SOCKTYPE ld[2];
667   SOCKTYPE *lsocks;
668
669   if (NULL == sigfd)
670   {
671     lsocks = NULL;
672   }
673   else
674   {
675     ld[0] = sigfd->fd;
676     ld[1] = -1;
677     lsocks = ld;
678   }
679   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (h->cfg,
680                                                           "arm",
681                                                           "PREFIX",
682                                                           &loprefix))
683     loprefix = GNUNET_strdup ("");
684   else
685     loprefix = GNUNET_CONFIGURATION_expand_dollar (h->cfg, loprefix);
686   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (h->cfg,
687                                                           "arm",
688                                                           "OPTIONS",
689                                                           &lopostfix))
690     lopostfix = GNUNET_strdup ("");
691   else
692     lopostfix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
693                                                     lopostfix);
694   if (GNUNET_OK !=
695       GNUNET_CONFIGURATION_get_value_string (h->cfg,
696                                              "arm",
697                                              "BINARY",
698                                              &cbinary))
699   {
700     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
701                                "arm",
702                                "BINARY");
703     GNUNET_free (loprefix);
704     GNUNET_free (lopostfix);
705     return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
706   }
707   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (h->cfg,
708                                                             "arm",
709                                                             "CONFIG",
710                                                             &config))
711     config = NULL;
712   binary = GNUNET_OS_get_libexec_binary_path (cbinary);
713   GNUNET_asprintf (&quotedbinary,
714                    "\"%s\"",
715                    binary);
716   GNUNET_free (cbinary);
717   if ( (GNUNET_YES ==
718         GNUNET_CONFIGURATION_have_value (h->cfg,
719                                          "TESTING",
720                                          "WEAKRANDOM")) &&
721        (GNUNET_YES ==
722         GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
723                                               "TESTING",
724                                               "WEAKRANDOM")) &&
725        (GNUNET_NO ==
726         GNUNET_CONFIGURATION_have_value (h->cfg,
727                                          "TESTING",
728                                          "HOSTFILE")) )
729   {
730     /* Means we are ONLY running locally */
731     /* we're clearly running a test, don't daemonize */
732     if (NULL == config)
733       proc = GNUNET_OS_start_process_s (GNUNET_NO,
734                                         std_inheritance,
735                                         lsocks,
736                                         loprefix,
737                                         quotedbinary,
738                                         /* no daemonization! */
739                                         lopostfix,
740                                         NULL);
741     else
742       proc = GNUNET_OS_start_process_s (GNUNET_NO,
743                                         std_inheritance,
744                                         lsocks,
745                                         loprefix,
746                                         quotedbinary,
747                                         "-c",
748                                         config,
749                                         /* no daemonization! */
750                                         lopostfix,
751                                         NULL);
752   }
753   else
754   {
755     if (NULL == config)
756       proc = GNUNET_OS_start_process_s (GNUNET_NO,
757                                         std_inheritance,
758                                         lsocks,
759                                         loprefix,
760                                         quotedbinary,
761                                         "-d",  /* do daemonize */
762                                         lopostfix,
763                                         NULL);
764     else
765       proc = GNUNET_OS_start_process_s (GNUNET_NO,
766                                         std_inheritance,
767                                         lsocks,
768                                         loprefix,
769                                         quotedbinary,
770                                         "-c",
771                                         config,
772                                         "-d",  /* do daemonize */
773                                         lopostfix,
774                                         NULL);
775   }
776   GNUNET_free (binary);
777   GNUNET_free (quotedbinary);
778   GNUNET_free_non_null (config);
779   GNUNET_free (loprefix);
780   GNUNET_free (lopostfix);
781   if (NULL == proc)
782     return GNUNET_ARM_RESULT_START_FAILED;
783   GNUNET_OS_process_destroy (proc);
784   return GNUNET_ARM_RESULT_STARTING;
785 }
786
787
788 /**
789  * Abort an operation.  Only prevents the callback from being
790  * called, the operation may still complete.
791  *
792  * @param op operation to cancel
793  */
794 void
795 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
796 {
797   struct GNUNET_ARM_Handle *h = op->h;
798
799   if (NULL != op->async)
800   {
801     GNUNET_SCHEDULER_cancel (op->async);
802     op->async = NULL;
803   }
804   if (NULL != op->rfd)
805   {
806     GNUNET_DISK_file_close (op->rfd);
807     op->rfd = NULL;
808   }
809   if (h->thm == op)
810   {
811     op->result_cont = NULL;
812     return;
813   }
814   GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
815                                h->operation_pending_tail,
816                                op);
817   GNUNET_free (op);
818 }
819
820
821 /**
822  * Start or stop a service.
823  *
824  * @param h handle to ARM
825  * @param service_name name of the service
826  * @param cb callback to invoke when service is ready
827  * @param cb_cls closure for @a cb
828  * @param type type of the request
829  * @return handle to queue, NULL on error
830  */
831 static struct GNUNET_ARM_Operation *
832 change_service (struct GNUNET_ARM_Handle *h,
833                 const char *service_name,
834                 GNUNET_ARM_ResultCallback cb,
835                 void *cb_cls,
836                 uint16_t type)
837 {
838   struct GNUNET_ARM_Operation *op;
839   size_t slen;
840   struct GNUNET_MQ_Envelope *env;
841   struct GNUNET_ARM_Message *msg;
842
843   slen = strlen (service_name) + 1;
844   if (slen + sizeof(struct GNUNET_ARM_Message) >= GNUNET_MAX_MESSAGE_SIZE)
845   {
846     GNUNET_break (0);
847     return NULL;
848   }
849   if (0 == h->request_id_counter)
850     h->request_id_counter++;
851   op = GNUNET_new (struct GNUNET_ARM_Operation);
852   op->h = h;
853   op->result_cont = cb;
854   op->cont_cls = cb_cls;
855   op->id = h->request_id_counter++;
856   GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
857                                     h->operation_pending_tail,
858                                     op);
859   env = GNUNET_MQ_msg_extra (msg, slen, type);
860   msg->reserved = htonl (0);
861   msg->request_id = GNUNET_htonll (op->id);
862   GNUNET_memcpy (&msg[1], service_name, slen);
863   GNUNET_MQ_send (h->mq, env);
864   return op;
865 }
866
867
868 /**
869  * Task run to notify application that ARM is already up.
870  *
871  * @param cls the operation that asked ARM to be started
872  */
873 static void
874 notify_running (void *cls)
875 {
876   struct GNUNET_ARM_Operation *op = cls;
877   struct GNUNET_ARM_Handle *h = op->h;
878
879   op->async = NULL;
880   GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
881                                h->operation_pending_tail,
882                                op);
883   if (NULL != op->result_cont)
884     op->result_cont (op->cont_cls,
885                      GNUNET_ARM_REQUEST_SENT_OK,
886                      GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
887   if ( (GNUNET_YES == h->currently_up) &&
888        (NULL != h->conn_status) )
889     h->conn_status (h->conn_status_cls,
890                     GNUNET_YES);
891   GNUNET_free (op);
892 }
893
894
895 /**
896  * Task run to notify application that ARM is being started.
897  *
898  * @param cls the operation that asked ARM to be started
899  */
900 static void
901 notify_starting (void *cls)
902 {
903   struct GNUNET_ARM_Operation *op = cls;
904   struct GNUNET_ARM_Handle *h = op->h;
905
906   op->async = NULL;
907   LOG (GNUNET_ERROR_TYPE_DEBUG,
908        "Notifying client that we started the ARM service\n");
909   GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
910                                h->operation_pending_tail,
911                                op);
912   if (NULL != op->result_cont)
913     op->result_cont (op->cont_cls,
914                      GNUNET_ARM_REQUEST_SENT_OK,
915                      op->starting_ret);
916   GNUNET_free (op);
917 }
918
919
920 /**
921  * Request for a service to be started.
922  *
923  * @param h handle to ARM
924  * @param service_name name of the service
925  * @param std_inheritance inheritance of std streams
926  * @param cont callback to invoke after request is sent or not sent
927  * @param cont_cls closure for @a cont
928  * @return handle for the operation, NULL on error
929  */
930 struct GNUNET_ARM_Operation *
931 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
932                                   const char *service_name,
933                                   enum GNUNET_OS_InheritStdioFlags
934                                   std_inheritance,
935                                   GNUNET_ARM_ResultCallback cont,
936                                   void *cont_cls)
937 {
938   struct GNUNET_ARM_Operation *op;
939   enum GNUNET_ARM_Result ret;
940   struct GNUNET_DISK_PipeHandle *sig;
941   struct GNUNET_DISK_FileHandle *wsig;
942
943   LOG (GNUNET_ERROR_TYPE_DEBUG,
944        "Starting service `%s'\n",
945        service_name);
946   if (0 != strcasecmp ("arm",
947                        service_name))
948     return change_service (h,
949                            service_name,
950                            cont,
951                            cont_cls,
952                            GNUNET_MESSAGE_TYPE_ARM_START);
953
954   /* Possible cases:
955    * 1) We're connected to ARM already. Invoke the callback immediately.
956    * 2) We're not connected to ARM.
957    *    Cancel any reconnection attempts temporarily, then perform
958    *    a service test.
959    */
960   if (GNUNET_YES == h->currently_up)
961   {
962     LOG (GNUNET_ERROR_TYPE_DEBUG,
963          "ARM is already running\n");
964     op = GNUNET_new (struct GNUNET_ARM_Operation);
965     op->h = h;
966     op->result_cont = cont;
967     op->cont_cls = cont_cls;
968     GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
969                                       h->operation_pending_tail,
970                                       op);
971     op->async = GNUNET_SCHEDULER_add_now (&notify_running, op);
972     return op;
973   }
974   /* This is an inherently uncertain choice, as it is of course
975      theoretically possible that ARM is up and we just did not
976      yet complete the MQ handshake.  However, given that users
977      are unlikely to hammer 'gnunet-arm -s' on a busy system,
978      the above check should catch 99.99% of the cases where ARM
979      is already running. */
980   LOG (GNUNET_ERROR_TYPE_DEBUG,
981        "Starting ARM service\n");
982   if (NULL == (sig = GNUNET_DISK_pipe (GNUNET_NO,
983                                        GNUNET_NO,
984                                        GNUNET_NO,
985                                        GNUNET_YES)))
986   {
987     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
988                          "pipe");
989   }
990   wsig = GNUNET_DISK_pipe_detach_end (sig,
991                                       GNUNET_DISK_PIPE_END_WRITE);
992   ret = start_arm_service (h,
993                            std_inheritance,
994                            wsig);
995   GNUNET_DISK_file_close (wsig);
996   if (GNUNET_ARM_RESULT_STARTING == ret)
997     reconnect_arm (h);
998   op = GNUNET_new (struct GNUNET_ARM_Operation);
999   op->h = h;
1000   op->result_cont = cont;
1001   op->cont_cls = cont_cls;
1002   GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
1003                                     h->operation_pending_tail,
1004                                     op);
1005   op->starting_ret = ret;
1006   if (NULL != sig)
1007   {
1008     op->rfd = GNUNET_DISK_pipe_detach_end (sig,
1009                                            GNUNET_DISK_PIPE_END_READ);
1010     /* Wait at most a minute for gnunet-service-arm to be up, as beyond
1011        that something clearly just went wrong */
1012     op->async = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_MINUTES,
1013                                                 op->rfd,
1014                                                 &notify_starting,
1015                                                 op);
1016   }
1017   else
1018   {
1019     op->async = GNUNET_SCHEDULER_add_now (&notify_starting,
1020                                           op);
1021   }
1022   GNUNET_DISK_pipe_close (sig);
1023   return op;
1024 }
1025
1026
1027 /**
1028  * Request a service to be stopped.  Stopping arm itself will not
1029  * invalidate its handle, and ARM API will try to restore connection
1030  * to the ARM service, even if ARM connection was lost because you
1031  * asked for ARM to be stopped.  Call
1032  * #GNUNET_ARM_disconnect() to free the handle and prevent
1033  * further connection attempts.
1034  *
1035  * @param h handle to ARM
1036  * @param service_name name of the service
1037  * @param cont callback to invoke after request is sent or is not sent
1038  * @param cont_cls closure for @a cont
1039  * @return handle for the operation, NULL on error
1040  */
1041 struct GNUNET_ARM_Operation *
1042 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
1043                                  const char *service_name,
1044                                  GNUNET_ARM_ResultCallback cont,
1045                                  void *cont_cls)
1046 {
1047   struct GNUNET_ARM_Operation *op;
1048
1049   LOG (GNUNET_ERROR_TYPE_DEBUG,
1050        "Stopping service `%s'\n",
1051        service_name);
1052   op = change_service (h,
1053                        service_name,
1054                        cont,
1055                        cont_cls,
1056                        GNUNET_MESSAGE_TYPE_ARM_STOP);
1057   if (NULL == op)
1058     return NULL;
1059   /* If the service is ARM, set a flag as we will use MQ errors
1060      to detect that the process is really gone. */
1061   if (0 == strcasecmp (service_name,
1062                        "arm"))
1063     op->is_arm_stop = GNUNET_YES;
1064   return op;
1065 }
1066
1067
1068 /**
1069  * Request a list of running services.
1070  *
1071  * @param h handle to ARM
1072  * @param cont callback to invoke after request is sent or is not sent
1073  * @param cont_cls closure for @a cont
1074  * @return handle for the operation, NULL on error
1075  */
1076 struct GNUNET_ARM_Operation *
1077 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
1078                                  GNUNET_ARM_ServiceListCallback cont,
1079                                  void *cont_cls)
1080 {
1081   struct GNUNET_ARM_Operation *op;
1082   struct GNUNET_MQ_Envelope *env;
1083   struct GNUNET_ARM_Message *msg;
1084
1085   LOG (GNUNET_ERROR_TYPE_DEBUG,
1086        "Requesting LIST from ARM service\n");
1087   if (0 == h->request_id_counter)
1088     h->request_id_counter++;
1089   op = GNUNET_new (struct GNUNET_ARM_Operation);
1090   op->h = h;
1091   op->list_cont = cont;
1092   op->cont_cls = cont_cls;
1093   op->id = h->request_id_counter++;
1094   GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
1095                                     h->operation_pending_tail,
1096                                     op);
1097   env = GNUNET_MQ_msg (msg,
1098                        GNUNET_MESSAGE_TYPE_ARM_LIST);
1099   msg->reserved = htonl (0);
1100   msg->request_id = GNUNET_htonll (op->id);
1101   GNUNET_MQ_send (h->mq, env);
1102   return op;
1103 }
1104
1105
1106 /* end of arm_api.c */