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