getting testing to work
[oweals/gnunet.git] / src / arm / arm_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 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 2, 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  */
26 #include "platform.h"
27 #include "gnunet_arm_service.h"
28 #include "gnunet_client_lib.h"
29 #include "gnunet_getopt_lib.h"
30 #include "gnunet_os_lib.h"
31 #include "gnunet_protocols.h"
32 #include "gnunet_server_lib.h"
33 #include "arm.h"
34
35
36 /**
37  * How long are we willing to wait for a service operation during the multi-operation
38  * request processing?
39  */
40 #define MULTI_TIMEOUT  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
41
42
43 /**
44  * Handle for interacting with ARM.
45  */ 
46 struct GNUNET_ARM_Handle
47 {
48
49   /**
50    * Our connection to the ARM service.
51    */
52   struct GNUNET_CLIENT_Connection *client;
53
54   /**
55    * The configuration that we are using.
56    */
57   const struct GNUNET_CONFIGURATION_Handle *cfg;
58
59   /**
60    * Scheduler to use.
61    */
62   struct GNUNET_SCHEDULER_Handle *sched;
63
64 };
65
66
67 /**
68  * Setup a context for communicating with ARM.  Note that this
69  * can be done even if the ARM service is not yet running.
70  *
71  * @param cfg configuration to use (needed to contact ARM;
72  *        the ARM service may internally use a different
73  *        configuration to determine how to start the service).
74  * @param sched scheduler to use
75  * @param service service that *this* process is implementing/providing, can be NULL
76  * @return context to use for further ARM operations, NULL on error
77  */
78 struct GNUNET_ARM_Handle *
79 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
80                     struct GNUNET_SCHEDULER_Handle *sched,
81                     const char *service)
82 {
83   struct GNUNET_ARM_Handle *ret;
84   struct GNUNET_CLIENT_Connection *client;
85
86   client = GNUNET_CLIENT_connect (sched, "arm", cfg);
87   if (client == NULL)
88     return NULL;
89   ret = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
90   ret->cfg = cfg;
91   ret->sched = sched;
92   ret->client = client;
93   return ret;
94 }
95
96
97 /**
98  * Disconnect from the ARM service.
99  *
100  * @param h the handle that was being used
101  */
102 void
103 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
104 {
105   if (h->client != NULL)
106     GNUNET_CLIENT_disconnect (h->client);
107   GNUNET_free (h);
108 }
109
110
111 /**
112  * Internal state for a request with ARM.
113  */
114 struct RequestContext
115 {
116
117   /**
118    * Pointer to our handle with ARM.
119    */
120   struct GNUNET_ARM_Handle *h;
121
122   /**
123    * Function to call with a status code for the requested operation.
124    */
125   GNUNET_ARM_Callback callback;
126
127   /**
128    * Closure for "callback".
129    */
130   void *cls;
131
132   /**
133    * Timeout for the operation.
134    */
135   struct GNUNET_TIME_Absolute timeout;
136
137   /**
138    * Type of the request expressed as a message type (start or stop).
139    */
140   uint16_t type;
141
142 };
143
144
145 /**
146  * A client specifically requested starting of ARM itself.
147  * This function is called with information about whether
148  * or not ARM is running; if it is, report success.  If
149  * it is not, start the ARM process.
150  *
151  * @param cls the context for the request that we will report on (struct RequestContext*)
152  * @param tc why were we called (reason says if ARM is running)
153  */
154 static void
155 arm_service_report (void *cls,
156                     const struct GNUNET_SCHEDULER_TaskContext *tc)
157 {
158   struct RequestContext *pos = cls;
159   pid_t pid;
160   char *binary;
161   char *config;
162
163   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE))
164     {
165       /* arm is running! */
166       if (pos->callback != NULL)
167         pos->callback (pos->cls, GNUNET_YES);
168       GNUNET_free (pos);
169       return;
170     }
171   /* FIXME: should we check that HOSTNAME for 'arm' is localhost? */
172   /* start service */
173   if (GNUNET_OK !=
174       GNUNET_CONFIGURATION_get_value_string (pos->h->cfg,
175                                              "arm",
176                                              "BINARY",
177                                              &binary))
178     {
179       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
180                   _("Configuration failes to specify option `%s' in section `%s'!\n"),
181                   "BINARY",
182                   "arm");
183       if (pos->callback != NULL)
184         pos->callback (pos->cls, GNUNET_SYSERR);
185       GNUNET_free (pos);
186       return;
187     }
188   if (GNUNET_OK !=
189       GNUNET_CONFIGURATION_get_value_filename (pos->h->cfg,
190                                                "arm", "CONFIG", &config))
191     {
192       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
193                   _("Configuration fails to specify option `%s' in section `%s'!\n"),
194                   "CONFIG",
195                   "arm");
196       if (pos->callback != NULL)
197         pos->callback (pos->cls, GNUNET_SYSERR);
198       GNUNET_free (binary);
199       GNUNET_free (pos);
200       return;
201     }
202   pid = GNUNET_OS_start_process (binary, binary, "-d", "-c", config,
203 #if DEBUG_ARM
204                                  "-L", "DEBUG",
205 #endif
206                                  NULL);
207   GNUNET_free (binary);
208   GNUNET_free (config);
209   if (pid == -1)
210     {
211       if (pos->callback != NULL)
212         pos->callback (pos->cls, GNUNET_SYSERR);
213       GNUNET_free (pos);
214       return;
215     }
216   if (pos->callback != NULL)
217     pos->callback (pos->cls, GNUNET_YES);
218   GNUNET_free (pos);
219 }
220
221
222 /**
223  * Process a response from ARM to a request for a change in service
224  * status.
225  *
226  * @param cls the request context 
227  * @param msg the response
228  */
229 static void
230 handle_response (void *cls, const struct GNUNET_MessageHeader *msg)
231 {
232   struct RequestContext *sc = cls;
233   int ret;
234
235   if (msg == NULL)
236     {
237       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
238                   _("Error receiving response from ARM service\n"));
239       GNUNET_CLIENT_disconnect (sc->h->client);
240       sc->h->client = GNUNET_CLIENT_connect (sc->h->sched, 
241                                              "arm", 
242                                              sc->h->cfg);
243       GNUNET_assert (NULL != sc->h->client);
244       if (sc->callback != NULL)
245         sc->callback (sc->cls, GNUNET_SYSERR);
246       GNUNET_free (sc);
247       return;
248     }
249 #if DEBUG_ARM
250   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
251               "Received response from ARM service: %u\n",
252               ntohs(msg->type));
253 #endif
254   switch (ntohs (msg->type))
255     {
256     case GNUNET_MESSAGE_TYPE_ARM_IS_UP:
257       ret = GNUNET_YES;
258       break;
259     case GNUNET_MESSAGE_TYPE_ARM_IS_DOWN:
260       ret = GNUNET_NO;
261       break;
262     case GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN:
263       ret = GNUNET_SYSERR;
264       break;
265     default:
266       GNUNET_break (0);
267       ret = GNUNET_SYSERR;
268     }
269   if (sc->callback != NULL)
270     sc->callback (sc->cls, ret);
271   GNUNET_free (sc);
272 }
273
274
275 /**
276  * Start or stop a service.
277  *
278  * @param h handle to ARM
279  * @param service_name name of the service
280  * @param timeout how long to wait before failing for good
281  * @param cb callback to invoke when service is ready
282  * @param cb_cls closure for callback
283  * @param type type of the request 
284  */
285 static void
286 change_service (struct GNUNET_ARM_Handle *h,
287                 const char *service_name,
288                 struct GNUNET_TIME_Relative timeout,
289                 GNUNET_ARM_Callback cb, void *cb_cls, uint16_t type)
290 {
291   struct RequestContext *sctx;
292   size_t slen;
293   struct GNUNET_MessageHeader *msg;
294
295   slen = strlen (service_name) + 1;
296   if (slen + sizeof (struct GNUNET_MessageHeader) >
297       GNUNET_SERVER_MAX_MESSAGE_SIZE)
298     {
299       GNUNET_break (0);
300       if (cb != NULL)
301         cb (cb_cls, GNUNET_NO);
302       return;
303     }
304 #if DEBUG_ARM
305   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
306               _("ARM requests starting of service `%s'.\n"), service_name);
307 #endif
308   sctx = GNUNET_malloc (sizeof (struct RequestContext) + slen);
309   sctx->h = h;
310   sctx->callback = cb;
311   sctx->cls = cb_cls;
312   sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
313   sctx->type = type;
314   msg = GNUNET_malloc (sizeof (struct GNUNET_MessageHeader) + slen);
315   msg->size = htons (sizeof (struct GNUNET_MessageHeader) + slen);
316   msg->type = htons (sctx->type);
317   memcpy (&msg[1], service_name, slen);
318   if (GNUNET_OK !=
319       GNUNET_CLIENT_transmit_and_get_response (sctx->h->client,
320                                                msg,
321                                                GNUNET_TIME_absolute_get_remaining (sctx->timeout),
322                                                GNUNET_YES,
323                                                &handle_response,
324                                                sctx))
325     {       
326       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
327                   _("Error while trying to transmit to ARM service\n"));
328       if (cb != NULL)
329         cb (cb_cls, GNUNET_SYSERR);
330       GNUNET_free (sctx);
331       GNUNET_free (msg);
332       return;
333     }
334   GNUNET_free (msg);
335 }
336
337
338 /**
339  * Start a service.
340  *
341  * @param h handle to ARM
342  * @param service_name name of the service
343  * @param timeout how long to wait before failing for good
344  * @param cb callback to invoke when service is ready
345  * @param cb_cls closure for callback
346  */
347 void
348 GNUNET_ARM_start_service (struct GNUNET_ARM_Handle *h,
349                           const char *service_name,
350                           struct GNUNET_TIME_Relative timeout,
351                           GNUNET_ARM_Callback cb, void *cb_cls)
352 {
353   struct RequestContext *sctx;
354
355   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
356               _("Starting service `%s'\n"), service_name);
357   if (0 == strcmp ("arm", service_name))
358     {
359       sctx = GNUNET_malloc (sizeof (struct RequestContext));
360       sctx->h = h;
361       sctx->callback = cb;
362       sctx->cls = cb_cls;
363       sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
364       GNUNET_CLIENT_service_test (h->sched,
365                                   "arm",
366                                   h->cfg, timeout, &arm_service_report, sctx);
367       return;
368     }
369   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_START);
370 }
371
372
373 /**
374  * Stop a service.
375  *
376  * @param h handle to ARM
377  * @param service_name name of the service
378  * @param timeout how long to wait before failing for good
379  * @param cb callback to invoke when service is ready
380  * @param cb_cls closure for callback
381  */
382 void
383 GNUNET_ARM_stop_service (struct GNUNET_ARM_Handle *h,
384                          const char *service_name,
385                          struct GNUNET_TIME_Relative timeout,
386                          GNUNET_ARM_Callback cb, void *cb_cls)
387 {
388   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
389               _("Stopping service `%s'\n"), service_name);
390   if (0 == strcmp ("arm", service_name))
391     {
392       GNUNET_CLIENT_service_shutdown (h->client);
393       h->client = NULL;
394       if (cb != NULL)
395         cb (cb_cls, GNUNET_NO);
396       return;
397     }
398   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_STOP);
399 }
400
401
402 /**
403  * Function to call for each service.
404  *
405  * @param h handle to ARM
406  * @param service_name name of the service
407  * @param timeout how long to wait before failing for good
408  * @param cb callback to invoke when service is ready
409  * @param cb_cls closure for callback
410  */
411 typedef void (*ServiceOperation) (struct GNUNET_ARM_Handle *h,
412                                   const char *service_name,
413                                   struct GNUNET_TIME_Relative timeout,
414                                   GNUNET_ARM_Callback cb, void *cb_cls);
415
416
417 /**
418  * Context for starting or stopping multiple services.
419  */
420 struct MultiContext
421 {
422   /**
423    * NULL-terminated array of services to start or stop.
424    */
425   char **services;
426
427   /**
428    * Our handle to ARM.
429    */
430   struct GNUNET_ARM_Handle *h;
431
432   /**
433    * Identifies the operation (start or stop).
434    */
435   ServiceOperation op;
436
437   /**
438    * Current position in "services".
439    */
440   unsigned int pos;
441 };
442
443
444 /**
445  * Run the operation for the next service in the multi-service
446  * request.
447  *
448  * @param cls the "struct MultiContext" that is being processed
449  * @param success status of the previous operation (ignored)
450  */
451 static void
452 next_operation (void *cls,
453                 int success)
454 {
455   struct MultiContext *mc = cls;
456   char *pos;
457   
458   if (NULL == (pos = mc->services[mc->pos]))
459     {
460       GNUNET_free (mc->services);
461       GNUNET_ARM_disconnect (mc->h);
462       GNUNET_free (mc);
463       return;
464     }
465   mc->pos++;
466   mc->op (mc->h, pos, MULTI_TIMEOUT, &next_operation, mc);
467   GNUNET_free (pos);
468 }
469
470
471 /**
472  * Run a multi-service request.
473  *
474  * @param cfg configuration to use (needed to contact ARM;
475  *        the ARM service may internally use a different
476  *        configuration to determine how to start the service).
477  * @param sched scheduler to use
478  * @param op the operation to perform for each service
479  * @param va NULL-terminated list of services
480  */
481 static void
482 run_multi_request (const struct GNUNET_CONFIGURATION_Handle *cfg,
483                    struct GNUNET_SCHEDULER_Handle *sched,                   
484                    ServiceOperation op,
485                    va_list va)
486 {
487   va_list cp;
488   unsigned int total;
489   struct MultiContext *mc;
490   struct GNUNET_ARM_Handle *h;
491   const char *c;
492   
493   h = GNUNET_ARM_connect (cfg, sched, NULL);
494   if (NULL == h)
495     {
496       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
497                   _("Error while trying to transmit to ARM service\n"));
498       return; 
499     }
500   total = 1;
501   va_copy (cp, va);
502   while (NULL != (va_arg (cp, const char*))) total++;
503   va_end (cp);
504   mc = GNUNET_malloc (sizeof(struct MultiContext));
505   mc->services = GNUNET_malloc (total * sizeof (char*));
506   mc->h = h;
507   mc->op = op;
508   total = 0;
509   va_copy (cp, va);
510   while (NULL != (c = va_arg (cp, const char*))) 
511     mc->services[total++] = GNUNET_strdup (c);
512   va_end (cp);
513   next_operation (mc, GNUNET_YES);
514 }
515
516
517 /**
518  * Start multiple services in the specified order.  Convenience
519  * function.  Works asynchronously, failures are not reported.
520  *
521  * @param cfg configuration to use (needed to contact ARM;
522  *        the ARM service may internally use a different
523  *        configuration to determine how to start the service).
524  * @param sched scheduler to use
525  * @param ... NULL-terminated list of service names (const char*)
526  */
527 void
528 GNUNET_ARM_start_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
529                            struct GNUNET_SCHEDULER_Handle *sched,
530                            ...)
531 {
532   va_list ap;
533
534   va_start (ap, sched);
535   run_multi_request (cfg, sched, &GNUNET_ARM_start_service, ap);
536   va_end (ap);
537 }
538
539
540 /**
541  * Stop multiple services in the specified order.  Convenience
542  * function.  Works asynchronously, failures are not reported.
543  *
544  * @param cfg configuration to use (needed to contact ARM;
545  *        the ARM service may internally use a different
546  *        configuration to determine how to start the service).
547  * @param sched scheduler to use
548  * @param ... NULL-terminated list of service names (const char*)
549  */
550 void
551 GNUNET_ARM_stop_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
552                           struct GNUNET_SCHEDULER_Handle *sched,
553                           ...)
554 {
555   va_list ap;
556
557   va_start (ap, sched);
558   run_multi_request (cfg, sched, &GNUNET_ARM_stop_service, ap);
559   va_end (ap);
560 }
561
562
563 /* end of arm_api.c */