- fix state selection
[oweals/gnunet.git] / src / arm / arm_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010, 2012, 2013 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
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  * Handle for interacting with ARM.
37  */
38 struct GNUNET_ARM_Handle
39 {
40   /**
41    * Our control connection to the ARM service.
42    */
43   struct GNUNET_CLIENT_Connection *client;
44
45   /**
46    * The configuration that we are using.
47    */
48   struct GNUNET_CONFIGURATION_Handle *cfg;
49
50   /**
51    * Handle for our current transmission request.
52    */
53   struct GNUNET_CLIENT_TransmitHandle *cth;
54
55   /**
56    * Head of doubly-linked list of pending requests.
57    */
58   struct ARMControlMessage *control_pending_head;
59
60   /**
61    * Tail of doubly-linked list of pending requests.
62    */
63   struct ARMControlMessage *control_pending_tail;
64
65   /**
66    * Head of doubly-linked list of sent requests.
67    */
68   struct ARMControlMessage *control_sent_head;
69
70   /**
71    * Tail of doubly-linked list of sent requests.
72    */
73   struct ARMControlMessage *control_sent_tail;
74
75   /**
76    * Callback to invoke on connection/disconnection.
77    */
78   GNUNET_ARM_ConnectionStatusCallback conn_status;
79
80   /**
81    * Closure for conn_status.
82    */
83   void *conn_status_cls;
84
85   /**
86    * ARM control message for the 'arm_termination_handler'
87    * with the continuation to call once the ARM shutdown is done.
88    */
89   struct ARMControlMessage *thm;
90
91   /**
92    * ID of the reconnect task (if any).
93    */
94   GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
95
96   /**
97    * Current delay we use for re-trying to connect to core.
98    */
99   struct GNUNET_TIME_Relative retry_backoff;
100
101   /**
102    * Counter for request identifiers
103    */
104   uint64_t request_id_counter;
105
106   /**
107    * Are we currently disconnected and hence unable to send?
108    */
109   unsigned char currently_down;
110
111   /**
112    * GNUNET_YES if we're running a service test.
113    */
114   unsigned char service_test_is_active;
115 };
116
117
118 /**
119  * Entry in a doubly-linked list of control messages to be transmitted
120  * to the arm service.
121  *
122  * The actual message is allocated at the end of this struct.
123  */
124 struct ARMControlMessage
125 {
126   /**
127    * This is a doubly-linked list.
128    */
129   struct ARMControlMessage *next;
130
131   /**
132    * This is a doubly-linked list.
133    */
134   struct ARMControlMessage *prev;
135
136   /**
137    * ARM handle.
138    */
139   struct GNUNET_ARM_Handle *h;
140
141   /**
142    * Message to send.
143    */
144   struct GNUNET_ARM_Message *msg;
145
146   /**
147    * Callback for service state change requests.
148    */
149   GNUNET_ARM_ResultCallback result_cont;
150
151   /**
152    * Callback for service list requests.
153    */
154   GNUNET_ARM_ServiceListCallback list_cont;
155
156   /**
157    * Closure for 'result_cont' or 'list_cont'.
158    */
159   void *cont_cls;
160
161   /**
162    * Timeout for the operation.
163    */
164   struct GNUNET_TIME_Absolute timeout;
165
166   /**
167    * Task to run when request times out.
168    */
169   GNUNET_SCHEDULER_TaskIdentifier timeout_task_id;
170
171   /**
172    * Flags for passing std descriptors to ARM (when starting ARM).
173    */
174   enum GNUNET_OS_InheritStdioFlags std_inheritance;
175
176   /**
177    * Type of the request expressed as a message type (start, stop or list).
178    */
179   uint16_t type;
180 };
181
182
183 /**
184  * Connect to arm.
185  *
186  * @param h arm handle
187  * @return GNUNET_OK on success, GNUNET_SYSERR on failure
188  */
189 static int
190 reconnect_arm (struct GNUNET_ARM_Handle *h);
191
192
193 /**
194  * Check the list of pending requests, send the next
195  * one to the arm.
196  *
197  * @param h arm handle
198  * @param ignore_currently_down transmit message even if not initialized?
199  */
200 static void
201 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down);
202
203
204 /**
205  * Task scheduled to try to re-connect to arm.
206  *
207  * @param cls the 'struct GNUNET_ARM_Handle'
208  * @param tc task context
209  */
210 static void
211 reconnect_arm_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
212 {
213   struct GNUNET_ARM_Handle *h = cls;
214
215   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
216   LOG (GNUNET_ERROR_TYPE_DEBUG, "Connecting to ARM service after delay\n");
217   reconnect_arm (h);
218 }
219
220
221 /**
222  * Close down any existing connection to the ARM service and
223  * try re-establishing it later.
224  *
225  * @param h our handle
226  */
227 static void
228 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
229 {
230   if (GNUNET_NO != h->currently_down)
231     return;
232   if (NULL != h->cth)
233   {
234     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
235     h->cth = NULL;
236   }
237   if (NULL != h->client)
238   {
239     GNUNET_CLIENT_disconnect (h->client);
240     h->client = NULL;
241   }
242   h->currently_down = GNUNET_YES;
243   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
244   h->reconnect_task =
245       GNUNET_SCHEDULER_add_delayed (h->retry_backoff, &reconnect_arm_task, h);
246   /* Don't clear pending messages on disconnection, deliver them later
247   clear_pending_messages (h, GNUNET_ARM_REQUEST_DISCONNECTED);
248   GNUNET_assert (NULL == h->control_pending_head);
249   */
250   h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
251   if (NULL != h->conn_status)
252     h->conn_status (h->conn_status_cls, GNUNET_NO);
253 }
254
255
256 /**
257  * Find a control message by its unique ID.
258  *
259  * @param h ARM handle
260  * @param id unique message ID to use for the lookup
261  * @return NULL if not found
262  */
263 static struct ARMControlMessage *
264 find_cm_by_id (struct GNUNET_ARM_Handle *h, uint64_t id)
265 {
266   struct ARMControlMessage *result;
267   for (result = h->control_sent_head; result; result = result->next)
268     if (id == result->msg->request_id)
269       return result;
270   return NULL;
271 }
272
273
274 /**
275  * Handler for ARM 'termination' reply (failure to receive).
276  *
277  * @param cls our "struct GNUNET_ARM_Handle"
278  * @param msg expected to be NULL
279  */
280 static void
281 arm_termination_handler (void *cls, const struct GNUNET_MessageHeader *msg)
282 {
283   struct GNUNET_ARM_Handle *h = cls;
284   struct ARMControlMessage *cm;
285
286   if (NULL != msg)
287   {
288     GNUNET_break (0);
289     GNUNET_CLIENT_receive (h->client, &arm_termination_handler, h,
290                            GNUNET_TIME_UNIT_FOREVER_REL);
291     return;
292   }
293   cm = h->thm;
294   h->thm = NULL;
295   h->currently_down = GNUNET_YES;
296   GNUNET_CLIENT_disconnect (h->client);
297   h->client = NULL;
298   if (NULL != cm->result_cont)
299     cm->result_cont (cm->cont_cls,
300                      GNUNET_ARM_REQUEST_SENT_OK,
301                      (const char *) &cm->msg[1],
302                      GNUNET_ARM_RESULT_STOPPED);
303   GNUNET_free (cm->msg);
304   GNUNET_free (cm);
305 }
306
307
308 /**
309  * Handler for ARM replies.
310  *
311  * @param cls our "struct GNUNET_ARM_Handle"
312  * @param msg the message received from the arm service
313  */
314 static void
315 client_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg)
316 {
317   struct GNUNET_ARM_Handle *h = cls;
318   const struct GNUNET_ARM_Message *arm_msg;
319   const struct GNUNET_ARM_ResultMessage *res;
320   const struct GNUNET_ARM_ListResultMessage *lres;
321   struct ARMControlMessage *cm;
322   const char **list;
323   const char *pos;
324   uint64_t id;
325   enum GNUNET_ARM_Result result;
326   uint16_t size_check;
327   uint16_t rcount;
328   uint16_t msize;
329   unsigned char fail;
330
331   list = NULL;
332   rcount = 0;
333   if (NULL == msg)
334   {
335     LOG (GNUNET_ERROR_TYPE_INFO,
336          _("Client was disconnected from arm service, trying to reconnect.\n"));
337     reconnect_arm_later (h);
338     return;
339   }
340   msize = ntohs (msg->size);
341   LOG (GNUNET_ERROR_TYPE_DEBUG,
342        "Processing message of type %u and size %u from arm service\n",
343        ntohs (msg->type), msize);
344   if (msize < sizeof (struct GNUNET_ARM_Message))
345   {
346     GNUNET_break (0);
347     reconnect_arm_later (h);
348     return;
349   }
350   arm_msg = (const struct GNUNET_ARM_Message *) msg;
351   GNUNET_break (0 == ntohl (arm_msg->reserved));
352   id = GNUNET_ntohll (arm_msg->request_id);
353   cm = find_cm_by_id (h, id);
354   if (NULL == cm)
355   {
356     LOG (GNUNET_ERROR_TYPE_DEBUG, "Message with unknown id %llu\n", id);
357     return;
358   }
359   fail = GNUNET_NO;
360   switch (ntohs (msg->type))
361   {
362   case GNUNET_MESSAGE_TYPE_ARM_RESULT:
363     if (msize < sizeof (struct GNUNET_ARM_ResultMessage))
364     {
365       GNUNET_assert (0);
366       fail = GNUNET_YES;
367     }
368     break;
369   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
370     if (msize < sizeof (struct GNUNET_ARM_ListResultMessage))
371     {
372       GNUNET_break (0);
373       fail = GNUNET_YES;
374       break;
375     }
376     size_check = 0;
377     lres = (const struct GNUNET_ARM_ListResultMessage *) msg;
378     rcount = ntohs (lres->count);
379     {
380       unsigned int i;
381
382       list = GNUNET_malloc (sizeof (const char *) * rcount);
383       pos = (const char *)&lres[1];
384       for (i = 0; i < rcount; i++)
385       {
386         const char *end = memchr (pos, 0, msize - size_check);
387         if (NULL == end)
388         {
389           GNUNET_break (0);
390           fail = GNUNET_YES;
391           break;
392         }
393         list[i] = pos;
394         size_check += (end - pos) + 1;
395         pos = end + 1;
396       }
397       if (GNUNET_YES == fail)
398       {
399         GNUNET_free (list);
400         list = NULL;
401       }
402     }
403     break;
404   default:
405     fail = GNUNET_YES;
406     break;
407   }
408   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
409   GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
410   GNUNET_CONTAINER_DLL_remove (h->control_sent_head,
411                                h->control_sent_tail, cm);
412   if (GNUNET_YES == fail)
413   {
414     reconnect_arm_later (h);
415     GNUNET_free (cm->msg);
416     GNUNET_free (cm);
417     return;
418   }
419   if ( (GNUNET_MESSAGE_TYPE_ARM_RESULT == ntohs (msg->type)) &&
420        (0 == strcasecmp ((const char *) &cm->msg[1],
421                          "arm")) &&
422        (NULL != (res = (const struct GNUNET_ARM_ResultMessage *) msg)) &&
423        (GNUNET_ARM_RESULT_STOPPING == ntohl (res->result)) )
424   {
425     /* special case: if we are stopping 'gnunet-service-arm', we do not just
426        wait for the result message, but also wait for the service to close
427        the connection (and then we have to close our client handle as well);
428        this is done by installing a different receive handler, waiting for
429        the connection to go down */
430     if (NULL != h->thm)
431     {
432       GNUNET_break (0);
433       cm->result_cont (h->thm->cont_cls,
434                        GNUNET_ARM_REQUEST_SENT_OK,
435                        (const char *) &h->thm->msg[1],
436                        GNUNET_ARM_RESULT_IS_NOT_KNOWN);
437       GNUNET_free (h->thm->msg);
438       GNUNET_free (h->thm);
439     }
440     h->thm = cm;
441     GNUNET_CLIENT_receive (h->client, &arm_termination_handler, h,
442                            GNUNET_TIME_UNIT_FOREVER_REL);
443     return;
444   }
445   GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
446                          GNUNET_TIME_UNIT_FOREVER_REL);
447   switch (ntohs (msg->type))
448   {
449   case GNUNET_MESSAGE_TYPE_ARM_RESULT:
450     res = (const struct GNUNET_ARM_ResultMessage *) msg;
451     LOG (GNUNET_ERROR_TYPE_DEBUG,
452          "Received response from ARM for service `%s': %u\n",
453          (const char *) &cm->msg[1], ntohs (msg->type));
454     result = (enum GNUNET_ARM_Result) ntohl (res->result);
455     if (NULL != cm->result_cont)
456       cm->result_cont (cm->cont_cls, GNUNET_ARM_REQUEST_SENT_OK,
457                        (const char *) &cm->msg[1], result);
458     break;
459   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
460     if (NULL != cm->list_cont)
461         cm->list_cont (cm->cont_cls, GNUNET_ARM_REQUEST_SENT_OK, rcount,
462                        list);
463     GNUNET_free (list);
464     break;
465   }
466   GNUNET_free (cm->msg);
467   GNUNET_free (cm);
468 }
469
470
471 /**
472  * Transmit the next message to the arm service.
473  *
474  * @param cls closure with the 'struct GNUNET_ARM_Handle'
475  * @param size number of bytes available in buf
476  * @param buf where the callee should write the message
477  * @return number of bytes written to buf
478  */
479 static size_t
480 transmit_arm_message (void *cls, size_t size, void *buf)
481 {
482   struct GNUNET_ARM_Handle *h = cls;
483   struct ARMControlMessage *cm;
484   struct GNUNET_ARM_Message *arm_msg;
485   uint64_t request_id;
486   int notify_connection;
487   uint16_t msize;
488
489   notify_connection = GNUNET_NO;
490   LOG (GNUNET_ERROR_TYPE_DEBUG,
491       "transmit_arm_message is running with %p buffer of size %lu. ARM is known to be %s\n",
492       buf, size, h->currently_down ? "unconnected" : "connected");
493   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
494   h->cth = NULL;
495   if ((GNUNET_YES == h->currently_down) && (NULL != buf))
496   {
497     h->currently_down = GNUNET_NO;
498     notify_connection = GNUNET_YES;
499     h->retry_backoff = GNUNET_TIME_UNIT_MILLISECONDS;
500     GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
501                            GNUNET_TIME_UNIT_FOREVER_REL);
502   }
503   if (NULL == buf)
504   {
505     LOG (GNUNET_ERROR_TYPE_DEBUG,
506          "Transmission failed, initiating reconnect\n");
507     reconnect_arm_later (h);
508     return 0;
509   }
510   if (NULL == (cm = h->control_pending_head))
511   {
512     LOG (GNUNET_ERROR_TYPE_DEBUG, "Queue is empty, not sending anything\n");
513     msize = 0;
514     goto end;
515   }
516   GNUNET_assert (NULL != cm->msg);
517   msize = ntohs (cm->msg->header.size);
518   if (size < msize)
519   {
520     LOG (GNUNET_ERROR_TYPE_DEBUG,
521         "Request is too big (%u < %u), not sending it\n", size, msize);
522     trigger_next_request (h, GNUNET_NO);
523     msize = 0;
524     goto end;
525   }
526   arm_msg = cm->msg;
527   if (0 == h->request_id_counter)
528     h->request_id_counter++;
529   request_id = h->request_id_counter++;
530   LOG (GNUNET_ERROR_TYPE_DEBUG,
531        "Transmitting control message with %u bytes of type %u to arm with id %llu\n",
532        (unsigned int) msize, (unsigned int) ntohs (cm->msg->header.type), request_id);
533   arm_msg->reserved = htonl (0);
534   arm_msg->request_id = GNUNET_htonll (request_id);
535   memcpy (buf, cm->msg, msize);
536   /* Otherwise we won't be able to find it later! */
537   arm_msg->request_id = request_id;
538   GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
539                                h->control_pending_tail, cm);
540   GNUNET_CONTAINER_DLL_insert_tail (h->control_sent_head,
541                                     h->control_sent_tail, cm);
542   /* Don't free msg, keep it around (kind of wasteful, but then we don't
543    * really have many messages to handle, and it'll be freed when it times
544    * out anyway.
545    */
546   trigger_next_request (h, GNUNET_NO);
547
548  end:
549   if ((GNUNET_YES == notify_connection) && (NULL != h->conn_status))
550     h->conn_status (h->conn_status_cls, GNUNET_YES);
551   return msize;
552 }
553
554
555 /**
556  * Check the list of pending requests, send the next
557  * one to the arm.
558  *
559  * @param h arm handle
560  * @param ignore_currently_down transmit message even if not initialized?
561  */
562 static void
563 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down)
564 {
565   uint16_t msize;
566
567   msize = sizeof (struct GNUNET_MessageHeader);
568   if ((GNUNET_YES == h->currently_down) && (ignore_currently_down == GNUNET_NO))
569   {
570     LOG (GNUNET_ERROR_TYPE_DEBUG,
571          "ARM connection down, not processing queue\n");
572     return;
573   }
574   if (NULL != h->cth)
575   {
576     LOG (GNUNET_ERROR_TYPE_DEBUG, "Request pending, not processing queue\n");
577     return;
578   }
579   if (NULL != h->control_pending_head)
580     msize =
581         ntohs (h->control_pending_head->msg->header.size);
582   else if (GNUNET_NO == ignore_currently_down)
583   {
584     LOG (GNUNET_ERROR_TYPE_DEBUG,
585          "Request queue empty, not processing queue\n");
586     return;                     /* no pending message */
587   }
588   h->cth =
589       GNUNET_CLIENT_notify_transmit_ready (h->client, msize,
590                                            GNUNET_TIME_UNIT_FOREVER_REL,
591                                            GNUNET_NO, &transmit_arm_message, h);
592 }
593
594
595 /**
596  * Connect to arm.
597  *
598  * @param h arm handle
599  * @return GNUNET_OK on success, GNUNET_SYSERR on failure
600  */
601 static int
602 reconnect_arm (struct GNUNET_ARM_Handle *h)
603 {
604   GNUNET_assert (NULL == h->client);
605   GNUNET_assert (GNUNET_YES == h->currently_down);
606   h->client = GNUNET_CLIENT_connect ("arm", h->cfg);
607   if (NULL == h->client)
608   {
609     LOG (GNUNET_ERROR_TYPE_DEBUG,
610            "arm_api, GNUNET_CLIENT_connect returned NULL\n");
611     if (NULL != h->conn_status)
612       h->conn_status (h->conn_status_cls, GNUNET_SYSERR);
613     return GNUNET_SYSERR;
614   }
615   LOG (GNUNET_ERROR_TYPE_DEBUG,
616          "arm_api, GNUNET_CLIENT_connect returned non-NULL\n");
617   trigger_next_request (h, GNUNET_YES);
618   return GNUNET_OK;
619 }
620
621
622 /**
623  * Set up a context for communicating with ARM, then
624  * start connecting to the ARM service using that context.
625  *
626  * @param cfg configuration to use (needed to contact ARM;
627  *        the ARM service may internally use a different
628  *        configuration to determine how to start the service).
629  * @param conn_status will be called when connecting/disconnecting
630  * @param cls closure for conn_status
631  * @return context to use for further ARM operations, NULL on error.
632  */
633 struct GNUNET_ARM_Handle *
634 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
635                     GNUNET_ARM_ConnectionStatusCallback conn_status, void *cls)
636 {
637   struct GNUNET_ARM_Handle *h;
638
639   h = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
640   h->cfg = GNUNET_CONFIGURATION_dup (cfg);
641   h->currently_down = GNUNET_YES;
642   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
643   h->conn_status = conn_status;
644   h->conn_status_cls = cls;
645   if (GNUNET_OK != reconnect_arm (h))
646   {
647     GNUNET_free (h);
648     return NULL;
649   }
650   return h;
651 }
652
653
654 /**
655  * Disconnect from the ARM service (if connected) and destroy the context.
656  *
657  * @param h the handle that was being used
658  */
659 void
660 GNUNET_ARM_disconnect_and_free (struct GNUNET_ARM_Handle *h)
661 {
662   struct ARMControlMessage *cm;
663
664   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
665   if (NULL != h->cth)
666   {
667     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
668     h->cth = NULL;
669   }
670   while ((NULL != (cm = h->control_pending_head))
671          || (NULL != (cm = h->control_sent_head)) )
672   {
673     if (NULL != h->control_pending_head)
674       GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
675                                    h->control_pending_tail, cm);
676     else
677       GNUNET_CONTAINER_DLL_remove (h->control_sent_head,
678                                    h->control_sent_tail, cm);
679     GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
680     GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
681     if (NULL != cm->result_cont)
682       cm->result_cont (cm->cont_cls, GNUNET_ARM_REQUEST_DISCONNECTED,
683                        NULL, 0);
684     /* FIXME: What about list callback? */
685     GNUNET_free_non_null (cm->msg);
686     GNUNET_free (cm);
687   }
688   if (NULL != h->client)
689   {
690     GNUNET_CLIENT_disconnect (h->client);
691     h->client = NULL;
692   }
693   if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
694   {
695     GNUNET_SCHEDULER_cancel (h->reconnect_task);
696     h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
697   }
698   if (GNUNET_NO == h->service_test_is_active)
699   {
700     GNUNET_CONFIGURATION_destroy (h->cfg);
701     GNUNET_free (h);
702   }
703 }
704
705
706 /**
707  * Message timed out. Remove it from the queue.
708  *
709  * @param cls the message (struct ARMControlMessage *)
710  * @param tc task context
711  */
712 static void
713 control_message_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
714 {
715   struct ARMControlMessage *cm = cls;
716   struct GNUNET_ARM_Message *arm_msg;
717
718   LOG (GNUNET_ERROR_TYPE_DEBUG,
719        "Control message timed out\n");
720   arm_msg = cm->msg;
721   if ((NULL == arm_msg) || (0 == arm_msg->request_id))
722   {
723     GNUNET_CONTAINER_DLL_remove (cm->h->control_pending_head,
724                                  cm->h->control_pending_tail, cm);
725   }
726   else
727   {
728     GNUNET_CONTAINER_DLL_remove (cm->h->control_sent_head,
729                                  cm->h->control_sent_tail, cm);
730   }
731   if (NULL != cm->result_cont)
732     cm->result_cont (cm->cont_cls, GNUNET_ARM_REQUEST_TIMEOUT, NULL, 0);
733   else if (NULL != cm->list_cont)
734     cm->list_cont (cm->cont_cls, GNUNET_ARM_REQUEST_TIMEOUT, 0, NULL);
735   GNUNET_free_non_null (cm->msg);
736   GNUNET_free (cm);
737 }
738
739
740 /**
741  * A client specifically requested starting of ARM itself.
742  * This function is called with information about whether
743  * or not ARM is running; if it is, report success.  If
744  * it is not, start the ARM process.
745  *
746  * @param cls the context for the request that we will report on (struct ARMControlMessage *)
747  * @param result GNUNET_YES if ARM is running
748  */
749 static void
750 arm_service_report (void *cls,
751                     int result)
752 {
753   struct ARMControlMessage *cm = cls;
754   struct GNUNET_ARM_Handle *h;
755   struct GNUNET_OS_Process *proc;
756   unsigned char test_is_active;
757   char *cbinary;
758   char *binary;
759   char *quotedbinary;
760   char *config;
761   char *loprefix;
762   char *lopostfix;
763
764   test_is_active = cm->h->service_test_is_active;
765   if ((GNUNET_YES == test_is_active) &&
766       (GNUNET_YES == result))
767   {
768     LOG (GNUNET_ERROR_TYPE_DEBUG,
769          "Looks like `%s' is already running.\n",
770          "gnunet-service-arm");
771     /* arm is running! */
772     if (cm->result_cont)
773       cm->result_cont (cm->cont_cls,
774                        GNUNET_ARM_REQUEST_SENT_OK, "arm",
775                        GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
776   }
777   if (GNUNET_NO == test_is_active)
778   {
779     /* User disconnected & destroyed ARM handle in the middle of
780      * the service test, so we kept the handle around until now.
781      */
782     GNUNET_CONFIGURATION_destroy (cm->h->cfg);
783     GNUNET_free (cm->h);
784   }
785   if ((GNUNET_YES == result) ||
786       (GNUNET_NO == test_is_active))
787   {
788     GNUNET_free (cm);
789     return;
790   }
791   cm->h->service_test_is_active = GNUNET_NO;
792   LOG (GNUNET_ERROR_TYPE_DEBUG,
793        "Looks like `%s' is not running, will start it.\n",
794        "gnunet-service-arm");
795   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
796       cm->h->cfg, "arm", "PREFIX", &loprefix))
797     loprefix = GNUNET_strdup ("");
798   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
799       cm->h->cfg, "arm", "OPTIONS", &lopostfix))
800     lopostfix = GNUNET_strdup ("");
801   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
802       cm->h->cfg, "arm", "BINARY", &cbinary))
803   {
804     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, "arm", "BINARY");
805     if (cm->result_cont)
806       cm->result_cont (cm->cont_cls,
807                        GNUNET_ARM_REQUEST_SENT_OK, "arm",
808                        GNUNET_ARM_RESULT_IS_NOT_KNOWN);
809     GNUNET_free (cm);
810     GNUNET_free (loprefix);
811     GNUNET_free (lopostfix);
812     return;
813   }
814   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (
815       cm->h->cfg, "arm", "CONFIG", &config))
816     config = NULL;
817   binary = GNUNET_OS_get_libexec_binary_path (cbinary);
818   GNUNET_asprintf (&quotedbinary,
819                    "\"%s\"",
820                    binary);
821   GNUNET_free (cbinary);
822   if ((GNUNET_YES == GNUNET_CONFIGURATION_have_value (
823           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
824       (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (
825           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
826       (GNUNET_NO == GNUNET_CONFIGURATION_have_value (
827           cm->h->cfg, "TESTING", "HOSTFILE")))
828   {
829     /* Means we are ONLY running locally */
830     /* we're clearly running a test, don't daemonize */
831     if (NULL == config)
832       proc = GNUNET_OS_start_process_s (GNUNET_NO, cm->std_inheritance,
833                                         NULL, loprefix, quotedbinary,
834                                         /* no daemonization! */
835                                         lopostfix, NULL);
836     else
837       proc = GNUNET_OS_start_process_s (GNUNET_NO, cm->std_inheritance,
838                                NULL, loprefix, quotedbinary, "-c", config,
839                                         /* no daemonization! */
840                                         lopostfix, NULL);
841   }
842   else
843   {
844     if (NULL == config)
845       proc = GNUNET_OS_start_process_s (GNUNET_NO, cm->std_inheritance,
846                                         NULL, loprefix, quotedbinary,
847                                         "-d", lopostfix, NULL);
848     else
849       proc = GNUNET_OS_start_process_s (GNUNET_NO, cm->std_inheritance,
850                                         NULL, loprefix, quotedbinary, "-c",
851                                         config,
852                                         "-d", lopostfix, NULL);
853   }
854   GNUNET_free (binary);
855   GNUNET_free (quotedbinary);
856   GNUNET_free_non_null (config);
857   GNUNET_free (loprefix);
858   GNUNET_free (lopostfix);
859   if (NULL == proc)
860   {
861     if (cm->result_cont)
862       cm->result_cont (cm->cont_cls, GNUNET_ARM_REQUEST_SENT_OK, "arm",
863           GNUNET_ARM_RESULT_START_FAILED);
864     GNUNET_free (cm);
865     return;
866   }
867   if (cm->result_cont)
868     cm->result_cont (cm->cont_cls, GNUNET_ARM_REQUEST_SENT_OK, "arm",
869         GNUNET_ARM_RESULT_STARTING);
870   GNUNET_OS_process_destroy (proc);
871   h = cm->h;
872   GNUNET_free (cm);
873   reconnect_arm (h);
874 }
875
876
877 /**
878  * Start or stop a service.
879  *
880  * @param h handle to ARM
881  * @param service_name name of the service
882  * @param timeout how long to wait before failing for good
883  * @param cb callback to invoke when service is ready
884  * @param cb_cls closure for callback
885  * @param type type of the request
886  */
887 static void
888 change_service (struct GNUNET_ARM_Handle *h, const char *service_name,
889                 struct GNUNET_TIME_Relative timeout, GNUNET_ARM_ResultCallback cb,
890                 void *cb_cls, uint16_t type)
891 {
892   struct ARMControlMessage *cm;
893   size_t slen;
894   struct GNUNET_ARM_Message *msg;
895
896   slen = strlen (service_name) + 1;
897   if (slen + sizeof (struct GNUNET_ARM_Message) >=
898       GNUNET_SERVER_MAX_MESSAGE_SIZE)
899   {
900     GNUNET_break (0);
901     if (cb != NULL)
902       cb (cb_cls, GNUNET_ARM_REQUEST_TOO_LONG, NULL, 0);
903     return;
904   }
905   LOG (GNUNET_ERROR_TYPE_DEBUG, "Requesting %s of service `%s'.\n",
906        (GNUNET_MESSAGE_TYPE_ARM_START == type) ? "start" : "termination",
907        service_name);
908   cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
909   cm->h = h;
910   cm->result_cont = cb;
911   cm->cont_cls = cb_cls;
912   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
913   memcpy (&cm[1], service_name, slen);
914   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message) + slen);
915   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message) + slen);
916   msg->header.type = htons (type);
917   msg->reserved = htonl (0);
918   memcpy (&msg[1], service_name, slen);
919   cm->msg = msg;
920   LOG (GNUNET_ERROR_TYPE_DEBUG,
921       "Inserting a control message into the queue. Timeout is %s\n",
922        GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_remaining (cm->timeout),
923                                                GNUNET_NO));
924   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
925                                     h->control_pending_tail, cm);
926   cm->timeout_task_id =
927       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
928                                     (cm->timeout), &control_message_timeout, cm);
929   trigger_next_request (h, GNUNET_NO);
930 }
931
932
933 /**
934  * Request for a service to be started.
935  *
936  * @param h handle to ARM
937  * @param service_name name of the service
938  * @param std_inheritance inheritance of std streams
939  * @param timeout how long to wait before failing for good
940  * @param cont callback to invoke after request is sent or not sent
941  * @param cont_cls closure for callback
942  */
943 void
944 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
945                                   const char *service_name,
946                                   enum GNUNET_OS_InheritStdioFlags std_inheritance,
947                                   struct GNUNET_TIME_Relative timeout,
948                                   GNUNET_ARM_ResultCallback cont,
949                                   void *cont_cls)
950 {
951   struct ARMControlMessage *cm;
952   size_t slen;
953
954   LOG (GNUNET_ERROR_TYPE_DEBUG,
955        "Asked to start service `%s' within %s\n", service_name,
956        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
957   if (0 == strcasecmp ("arm", service_name))
958   {
959     /* Possible cases:
960      * 1) We're connected to ARM already. Invoke the callback immediately.
961      * 2) We're not connected to ARM.
962      *    Cancel any reconnection attempts temporarily, then perform
963      *    a service test.
964      */
965     if (GNUNET_NO == h->currently_down)
966     {
967       LOG (GNUNET_ERROR_TYPE_DEBUG, "ARM is already running\n");
968       if (NULL != cont)
969         cont (cont_cls, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
970     }
971     else if (GNUNET_NO == h->service_test_is_active)
972     {
973       if (NULL != h->cth)
974       {
975         GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
976         h->cth = NULL;
977       }
978       if (NULL != h->client)
979       {
980         GNUNET_CLIENT_disconnect (h->client);
981         h->client = NULL;
982       }
983       if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
984       {
985         GNUNET_SCHEDULER_cancel (h->reconnect_task);
986         h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
987       }
988
989       LOG (GNUNET_ERROR_TYPE_DEBUG,
990           "Not connected to ARM, will do a service test\n");
991
992       slen = strlen ("arm") + 1;
993       cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
994       cm->h = h;
995       cm->result_cont = cont;
996       cm->cont_cls = cont_cls;
997       cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
998       cm->std_inheritance = std_inheritance;
999       memcpy (&cm[1], service_name, slen);
1000       h->service_test_is_active = GNUNET_YES;
1001       GNUNET_CLIENT_service_test ("arm", h->cfg, timeout, &arm_service_report,
1002                                   cm);
1003     }
1004     else
1005     {
1006       /* Service test is already running - tell user to chill out and try
1007        * again later.
1008        */
1009       LOG (GNUNET_ERROR_TYPE_DEBUG, "Service test is already in progress, we're busy\n");
1010       if (NULL != cont)
1011         cont (cont_cls, GNUNET_ARM_REQUEST_BUSY, NULL, 0);
1012     }
1013     return;
1014   }
1015   change_service (h, service_name, timeout, cont, cont_cls,
1016                   GNUNET_MESSAGE_TYPE_ARM_START);
1017 }
1018
1019
1020 /**
1021  * Request a service to be stopped.
1022  * Stopping arm itself will not invalidate its handle, and
1023  * ARM API will try to restore connection to the ARM service,
1024  * even if ARM connection was lost because you asked for ARM to be stopped.
1025  * Call GNUNET_ARM_disconnect_and_free () to free the handle and prevent
1026  * further connection attempts.
1027  *
1028  * @param h handle to ARM
1029  * @param service_name name of the service
1030  * @param timeout how long to wait before failing for good
1031  * @param cont callback to invoke after request is sent or is not sent
1032  * @param cont_cls closure for callback
1033  */
1034 void
1035 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
1036                                  const char *service_name,
1037                                  struct GNUNET_TIME_Relative timeout,
1038                                  GNUNET_ARM_ResultCallback cont,
1039                                  void *cont_cls)
1040 {
1041   LOG (GNUNET_ERROR_TYPE_DEBUG,
1042        "Stopping service `%s' within %s\n",
1043        service_name,
1044        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
1045   change_service (h, service_name, timeout, cont, cont_cls,
1046                   GNUNET_MESSAGE_TYPE_ARM_STOP);
1047 }
1048
1049
1050 /**
1051  * Request a list of running services.
1052  *
1053  * @param h handle to ARM
1054  * @param timeout how long to wait before failing for good
1055  * @param cont callback to invoke after request is sent or is not sent
1056  * @param cont_cls closure for callback
1057  */
1058 void
1059 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
1060                                  struct GNUNET_TIME_Relative timeout,
1061                                  GNUNET_ARM_ServiceListCallback cont,
1062                                  void *cont_cls)
1063 {
1064   struct ARMControlMessage *cm;
1065   struct GNUNET_ARM_Message *msg;
1066
1067   LOG (GNUNET_ERROR_TYPE_DEBUG,
1068        "Requesting LIST from ARM service with timeout: %s\n",
1069        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_YES));
1070   cm = GNUNET_malloc (sizeof (struct ARMControlMessage));
1071   cm->h = h;
1072   cm->list_cont = cont;
1073   cm->cont_cls = cont_cls;
1074   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
1075   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message));
1076   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message));
1077   msg->header.type = htons (GNUNET_MESSAGE_TYPE_ARM_LIST);
1078   msg->reserved = htonl (0);
1079   cm->msg = msg;
1080   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
1081                                     h->control_pending_tail, cm);
1082   cm->timeout_task_id =
1083       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
1084                                     (cm->timeout), &control_message_timeout, cm);
1085   trigger_next_request (h, GNUNET_NO);
1086 }
1087
1088
1089 /* end of arm_api.c */