better support for daemons
[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 struct ArmContext
37 {
38   GNUNET_ARM_Callback callback;
39   void *cls;
40   char *service_name;
41   struct GNUNET_CLIENT_Connection *client;
42   struct GNUNET_CONFIGURATION_Handle *cfg;
43   struct GNUNET_TIME_Absolute timeout;
44   uint16_t type;
45 };
46
47
48 static void
49 arm_service_report (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
50 {
51   struct ArmContext *pos = cls;
52   pid_t pid;
53   char *binary;
54   char *config;
55
56   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE))
57     {
58       if (pos->callback != NULL)
59         pos->callback (pos->cls, GNUNET_YES);
60       GNUNET_free (pos);
61       return;
62     }
63   binary = NULL;
64   config = NULL;
65   /* start service */
66   if ((GNUNET_OK !=
67        GNUNET_CONFIGURATION_get_value_filename (pos->cfg,
68                                                 "arm",
69                                                 "BINARY",
70                                                 &binary)) ||
71       (GNUNET_OK !=
72        GNUNET_CONFIGURATION_get_value_filename (pos->cfg,
73                                                 "arm", "CONFIG", &config)))
74     {
75       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
76                   _("Configuration file or binary for ARM not known!\n"));
77       if (pos->callback != NULL)
78         pos->callback (pos->cls, GNUNET_SYSERR);
79       GNUNET_free_non_null (binary);
80       GNUNET_free (pos);
81       return;
82     }
83   pid = GNUNET_OS_start_process (binary, binary, "-d", "-c", config,
84 #if DEBUG_ARM
85                                  "-L", "DEBUG",
86 #endif
87                                  NULL);
88   GNUNET_free (binary);
89   GNUNET_free (config);
90   if (pid == -1)
91     {
92       if (pos->callback != NULL)
93         pos->callback (pos->cls, GNUNET_SYSERR);
94       GNUNET_free (pos);
95       return;
96     }
97   /* FIXME: consider checking again to see if it worked!? */
98   if (pos->callback != NULL)
99     pos->callback (pos->cls, GNUNET_YES);
100   GNUNET_free (pos);
101 }
102
103
104 static void
105 handle_response (void *cls, const struct GNUNET_MessageHeader *msg)
106 {
107   struct ArmContext *sc = cls;
108   int ret;
109
110   if (msg == NULL)
111     {
112       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
113                   _("Error receiving response from ARM service\n"));
114       GNUNET_CLIENT_disconnect (sc->client);
115       if (sc->callback != NULL)
116         sc->callback (sc->cls, GNUNET_SYSERR);
117       GNUNET_free (sc);
118       return;
119     }
120 #if DEBUG_ARM
121   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
122               _("Received response from ARM service\n"));
123 #endif
124   switch (ntohs (msg->type))
125     {
126     case GNUNET_MESSAGE_TYPE_ARM_IS_UP:
127       ret = GNUNET_YES;
128       break;
129     case GNUNET_MESSAGE_TYPE_ARM_IS_DOWN:
130       ret = GNUNET_NO;
131       break;
132     case GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN:
133       ret = GNUNET_SYSERR;
134       break;
135     default:
136       GNUNET_break (0);
137       ret = GNUNET_SYSERR;
138     }
139   GNUNET_CLIENT_disconnect (sc->client);
140   if (sc->callback != NULL)
141     sc->callback (sc->cls, ret);
142   GNUNET_free (sc);
143 }
144
145
146 static size_t
147 send_service_msg (void *cls, size_t size, void *buf)
148 {
149   struct ArmContext *sctx = cls;
150   struct GNUNET_MessageHeader *msg;
151   size_t slen;
152
153   if (buf == NULL)
154     {
155       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
156                   _("Error while trying to transmit to ARM service\n"));
157       GNUNET_CLIENT_disconnect (sctx->client);
158       if (sctx->callback != NULL)
159         sctx->callback (sctx->cls, GNUNET_SYSERR);
160       GNUNET_free (sctx->service_name);
161       GNUNET_free (sctx);
162       return 0;
163     }
164 #if DEBUG_ARM
165   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
166               _("Transmitting service request to ARM.\n"));
167 #endif
168   slen = strlen (sctx->service_name) + 1;
169   GNUNET_assert (size >= slen);
170   msg = buf;
171   msg->size = htons (sizeof (struct GNUNET_MessageHeader) + slen);
172   msg->type = htons (sctx->type);
173   memcpy (&msg[1], sctx->service_name, slen);
174   GNUNET_free (sctx->service_name);
175   sctx->service_name = NULL;
176   GNUNET_CLIENT_receive (sctx->client,
177                          &handle_response,
178                          sctx,
179                          GNUNET_TIME_absolute_get_remaining (sctx->timeout));
180   return slen + sizeof (struct GNUNET_MessageHeader);
181 }
182
183
184 /**
185  * Start or stop a service.
186  *
187  * @param service_name name of the service
188  * @param cfg configuration to use (needed to contact ARM;
189  *        the ARM service may internally use a different
190  *        configuration to determine how to start the service).
191  * @param sched scheduler to use
192  * @param timeout how long to wait before failing for good
193  * @param cb callback to invoke when service is ready
194  * @param cb_cls closure for callback
195  */
196 static void
197 change_service (const char *service_name,
198                 struct GNUNET_CONFIGURATION_Handle *cfg,
199                 struct GNUNET_SCHEDULER_Handle *sched,
200                 struct GNUNET_TIME_Relative timeout,
201                 GNUNET_ARM_Callback cb, void *cb_cls, uint16_t type)
202 {
203   struct GNUNET_CLIENT_Connection *client;
204   struct ArmContext *sctx;
205   size_t slen;
206
207   slen = strlen (service_name) + 1;
208   if (slen + sizeof (struct GNUNET_MessageHeader) >
209       GNUNET_SERVER_MAX_MESSAGE_SIZE)
210     {
211       GNUNET_break (0);
212       if (cb != NULL)
213         cb (cb_cls, GNUNET_NO);
214       return;
215     }
216   client = GNUNET_CLIENT_connect (sched, "arm", cfg);
217   if (client == NULL)
218     {
219       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
220                   _("Failed to connect to ARM service\n"));
221       if (cb != NULL)
222         cb (cb_cls, GNUNET_SYSERR);
223       return;
224     }
225 #if DEBUG_ARM
226   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
227               _("ARM requests starting of service `%s'.\n"), service_name);
228 #endif
229   sctx = GNUNET_malloc (sizeof (struct ArmContext));
230   sctx->callback = cb;
231   sctx->cls = cb_cls;
232   sctx->client = client;
233   sctx->service_name = GNUNET_strdup (service_name);
234   sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
235   sctx->type = type;
236   if (NULL ==
237       GNUNET_CLIENT_notify_transmit_ready (client,
238                                            slen +
239                                            sizeof (struct
240                                                    GNUNET_MessageHeader),
241                                            timeout, &send_service_msg, sctx))
242     {
243       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
244                   _("Failed to transmit request to ARM service\n"));
245       GNUNET_free (sctx->service_name);
246       GNUNET_free (sctx);
247       if (cb != NULL)
248         cb (cb_cls, GNUNET_SYSERR);
249       GNUNET_CLIENT_disconnect (client);
250       return;
251     }
252 }
253
254
255 /**
256  * Start a service.
257  *
258  * @param service_name name of the service
259  * @param cfg configuration to use (needed to contact ARM;
260  *        the ARM service may internally use a different
261  *        configuration to determine how to start the service).
262  * @param sched scheduler to use
263  * @param timeout how long to wait before failing for good
264  * @param cb callback to invoke when service is ready
265  * @param cb_cls closure for callback
266  */
267 void
268 GNUNET_ARM_start_service (const char *service_name,
269                           struct GNUNET_CONFIGURATION_Handle *cfg,
270                           struct GNUNET_SCHEDULER_Handle *sched,
271                           struct GNUNET_TIME_Relative timeout,
272                           GNUNET_ARM_Callback cb, void *cb_cls)
273 {
274   struct ArmContext *sctx;
275
276   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
277               _("Starting service `%s'\n"), service_name);
278   if (0 == strcmp ("arm", service_name))
279     {
280       sctx = GNUNET_malloc (sizeof (struct ArmContext));
281       sctx->callback = cb;
282       sctx->cls = cb_cls;
283       sctx->cfg = cfg;
284       GNUNET_CLIENT_service_test (sched,
285                                   "arm",
286                                   cfg, timeout, &arm_service_report, sctx);
287       return;
288     }
289   change_service (service_name,
290                   cfg,
291                   sched, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_START);
292 }
293
294
295
296
297 /**
298  * Stop a service.
299  *
300  * @param service_name name of the service
301  * @param cfg configuration to use (needed to contact ARM;
302  *        the ARM service may internally use a different
303  *        configuration to determine how to start the service).
304  * @param sched scheduler to use
305  * @param timeout how long to wait before failing for good
306  * @param cb callback to invoke when service is ready
307  * @param cb_cls closure for callback
308  */
309 void
310 GNUNET_ARM_stop_service (const char *service_name,
311                          struct GNUNET_CONFIGURATION_Handle *cfg,
312                          struct GNUNET_SCHEDULER_Handle *sched,
313                          struct GNUNET_TIME_Relative timeout,
314                          GNUNET_ARM_Callback cb, void *cb_cls)
315 {
316   struct GNUNET_CLIENT_Connection *client;
317
318   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
319               _("Stopping service `%s'\n"), service_name);
320   if (0 == strcmp ("arm", service_name))
321     {
322       client = GNUNET_CLIENT_connect (sched, "arm", cfg);
323       if (client == NULL)
324         {
325           if (cb != NULL)
326             cb (cb_cls, GNUNET_SYSERR);
327           return;
328         }
329       GNUNET_CLIENT_service_shutdown (client);
330       GNUNET_CLIENT_disconnect (client);
331       if (cb != NULL)
332         cb (cb_cls, GNUNET_NO);
333       return;
334     }
335   change_service (service_name,
336                   cfg,
337                   sched, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_STOP);
338 }
339
340 /* end of arm_api.c */