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