2 This file is part of GNUnet.
3 Copyright (C) 2009, 2010, 2012, 2013, 2016 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
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 Affero General Public License for more details.
18 * @brief API for accessing the ARM service
19 * @author Christian Grothoff
23 #include "gnunet_util_lib.h"
24 #include "gnunet_arm_service.h"
25 #include "gnunet_protocols.h"
28 #define LOG(kind,...) GNUNET_log_from (kind, "arm-api",__VA_ARGS__)
32 * Entry in a doubly-linked list of operations awaiting for replies
33 * (in-order) from the ARM service.
35 struct GNUNET_ARM_Operation
38 * This is a doubly-linked list.
40 struct GNUNET_ARM_Operation *next;
43 * This is a doubly-linked list.
45 struct GNUNET_ARM_Operation *prev;
50 struct GNUNET_ARM_Handle *h;
53 * Callback for service state change requests.
55 GNUNET_ARM_ResultCallback result_cont;
58 * Callback for service list requests.
60 GNUNET_ARM_ServiceListCallback list_cont;
63 * Closure for @e result_cont or @e list_cont.
68 * Task for async completion.
70 struct GNUNET_SCHEDULER_Task *async;
73 * Unique ID for the request.
78 * Result of this operation for #notify_starting().
80 enum GNUNET_ARM_Result starting_ret;
83 * Is this an operation to stop the ARM service?
90 * Handle for interacting with ARM.
92 struct GNUNET_ARM_Handle
95 * Our connection to the ARM service.
97 struct GNUNET_MQ_Handle *mq;
100 * The configuration that we are using.
102 const struct GNUNET_CONFIGURATION_Handle *cfg;
105 * Head of doubly-linked list of pending operations.
107 struct GNUNET_ARM_Operation *operation_pending_head;
110 * Tail of doubly-linked list of pending operations.
112 struct GNUNET_ARM_Operation *operation_pending_tail;
115 * Callback to invoke on connection/disconnection.
117 GNUNET_ARM_ConnectionStatusCallback conn_status;
120 * Closure for @e conn_status.
122 void *conn_status_cls;
125 * ARM operation where the goal is to wait for ARM shutdown to
126 * complete. This operation is special in that it waits for an
127 * error on the @e mq. So we complete it by calling the
128 * continuation in the #mq_error_handler(). Note that the operation
129 * is no longer in the @e operation_pending_head DLL once it is
130 * referenced from this field.
132 struct GNUNET_ARM_Operation *thm;
135 * ID of the reconnect task (if any).
137 struct GNUNET_SCHEDULER_Task *reconnect_task;
140 * Current delay we use for re-trying to connect to core.
142 struct GNUNET_TIME_Relative retry_backoff;
145 * Counter for request identifiers. They are used to match replies
146 * from ARM to operations in the @e operation_pending_head DLL.
148 uint64_t request_id_counter;
151 * Have we detected that ARM is up?
161 * @param h arm handle
162 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
165 reconnect_arm (struct GNUNET_ARM_Handle *h);
169 * Task scheduled to try to re-connect to arm.
171 * @param cls the `struct GNUNET_ARM_Handle`
174 reconnect_arm_task (void *cls)
176 struct GNUNET_ARM_Handle *h = cls;
178 h->reconnect_task = NULL;
184 * Close down any existing connection to the ARM service and
185 * try re-establishing it later.
187 * @param h our handle
190 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
192 struct GNUNET_ARM_Operation *op;
196 GNUNET_MQ_destroy (h->mq);
199 h->currently_up = GNUNET_NO;
200 GNUNET_assert (NULL == h->reconnect_task);
202 GNUNET_SCHEDULER_add_delayed (h->retry_backoff,
205 while (NULL != (op = h->operation_pending_head))
207 if (NULL != op->result_cont)
208 op->result_cont (op->cont_cls,
209 GNUNET_ARM_REQUEST_DISCONNECTED,
211 if (NULL != op->list_cont)
212 op->list_cont (op->cont_cls,
213 GNUNET_ARM_REQUEST_DISCONNECTED,
216 GNUNET_ARM_operation_cancel (op);
218 GNUNET_assert (NULL == h->operation_pending_head);
219 h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
220 if (NULL != h->conn_status)
221 h->conn_status (h->conn_status_cls,
227 * Find a control message by its unique ID.
229 * @param h ARM handle
230 * @param id unique message ID to use for the lookup
231 * @return NULL if not found
233 static struct GNUNET_ARM_Operation *
234 find_op_by_id (struct GNUNET_ARM_Handle *h,
237 struct GNUNET_ARM_Operation *result;
239 for (result = h->operation_pending_head; NULL != result; result = result->next)
240 if (id == result->id)
247 * Handler for ARM replies.
249 * @param cls our `struct GNUNET_ARM_Handle`
250 * @param res the message received from the arm service
253 handle_arm_result (void *cls,
254 const struct GNUNET_ARM_ResultMessage *res)
256 struct GNUNET_ARM_Handle *h = cls;
257 struct GNUNET_ARM_Operation *op;
259 enum GNUNET_ARM_Result result;
260 GNUNET_ARM_ResultCallback result_cont;
261 void *result_cont_cls;
263 id = GNUNET_ntohll (res->arm_msg.request_id);
264 op = find_op_by_id (h,
268 LOG (GNUNET_ERROR_TYPE_DEBUG,
269 "Message with unknown id %llu\n",
270 (unsigned long long) id);
274 result = (enum GNUNET_ARM_Result) ntohl (res->result);
275 if ( (GNUNET_YES == op->is_arm_stop) &&
276 (GNUNET_ARM_RESULT_STOPPING == result) )
278 /* special case: if we are stopping 'gnunet-service-arm', we do not just
279 wait for the result message, but also wait for the service to close
280 the connection (and then we have to close our client handle as well);
281 this is done by installing a different receive handler, waiting for
282 the connection to go down */
286 op->result_cont (h->thm->cont_cls,
287 GNUNET_ARM_REQUEST_SENT_OK,
288 GNUNET_ARM_RESULT_IS_NOT_KNOWN);
289 GNUNET_free (h->thm);
291 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
292 h->operation_pending_tail,
297 result_cont = op->result_cont;
298 result_cont_cls = op->cont_cls;
299 GNUNET_ARM_operation_cancel (op);
300 if (NULL != result_cont)
301 result_cont (result_cont_cls,
302 GNUNET_ARM_REQUEST_SENT_OK,
308 * Checked that list result message is well-formed.
310 * @param cls our `struct GNUNET_ARM_Handle`
311 * @param lres the message received from the arm service
312 * @return #GNUNET_OK if message is well-formed
315 check_arm_list_result (void *cls,
316 const struct GNUNET_ARM_ListResultMessage *lres)
318 const char *pos = (const char *) &lres[1];
319 uint16_t rcount = ntohs (lres->count);
320 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
324 for (unsigned int i = 0; i < rcount; i++)
326 const char *end = memchr (pos, 0, msize - size_check);
330 return GNUNET_SYSERR;
332 size_check += (end - pos) + 1;
340 * Handler for ARM list replies.
342 * @param cls our `struct GNUNET_ARM_Handle`
343 * @param lres the message received from the arm service
346 handle_arm_list_result (void *cls,
347 const struct GNUNET_ARM_ListResultMessage *lres)
349 struct GNUNET_ARM_Handle *h = cls;
350 uint16_t rcount = ntohs (lres->count);
351 const char *list[rcount];
352 const char *pos = (const char *) &lres[1];
353 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
354 struct GNUNET_ARM_Operation *op;
358 id = GNUNET_ntohll (lres->arm_msg.request_id);
359 op = find_op_by_id (h,
363 LOG (GNUNET_ERROR_TYPE_DEBUG,
364 "Message with unknown id %llu\n",
365 (unsigned long long) id);
369 for (unsigned int i = 0; i < rcount; i++)
371 const char *end = memchr (pos,
375 /* Assert, as this was already checked in #check_arm_list_result() */
376 GNUNET_assert (NULL != end);
378 size_check += (end - pos) + 1;
381 if (NULL != op->list_cont)
382 op->list_cont (op->cont_cls,
383 GNUNET_ARM_REQUEST_SENT_OK,
386 GNUNET_ARM_operation_cancel (op);
391 * Receive confirmation from test, ARM service is up.
393 * @param cls closure with the `struct GNUNET_ARM_Handle`
394 * @param msg message received
397 handle_confirm (void *cls,
398 const struct GNUNET_MessageHeader *msg)
400 struct GNUNET_ARM_Handle *h = cls;
402 LOG (GNUNET_ERROR_TYPE_DEBUG,
403 "Got confirmation from ARM that we are up!\n");
404 if (GNUNET_NO == h->currently_up)
406 h->currently_up = GNUNET_YES;
407 if (NULL != h->conn_status)
408 h->conn_status (h->conn_status_cls,
415 * Generic error handler, called with the appropriate error code and
416 * the same closure specified at the creation of the message queue.
417 * Not every message queue implementation supports an error handler.
419 * @param cls closure with the `struct GNUNET_ARM_Handle *`
420 * @param error error code
423 mq_error_handler (void *cls,
424 enum GNUNET_MQ_Error error)
426 struct GNUNET_ARM_Handle *h = cls;
427 struct GNUNET_ARM_Operation *op;
429 h->currently_up = GNUNET_NO;
430 if (NULL != (op = h->thm))
433 op->result_cont (op->cont_cls,
434 GNUNET_ARM_REQUEST_SENT_OK,
435 GNUNET_ARM_RESULT_STOPPED);
438 reconnect_arm_later (h);
445 * @param h arm handle
446 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
449 reconnect_arm (struct GNUNET_ARM_Handle *h)
451 struct GNUNET_MQ_MessageHandler handlers[] = {
452 GNUNET_MQ_hd_fixed_size (arm_result,
453 GNUNET_MESSAGE_TYPE_ARM_RESULT,
454 struct GNUNET_ARM_ResultMessage,
456 GNUNET_MQ_hd_var_size (arm_list_result,
457 GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT,
458 struct GNUNET_ARM_ListResultMessage,
460 GNUNET_MQ_hd_fixed_size (confirm,
461 GNUNET_MESSAGE_TYPE_ARM_TEST,
462 struct GNUNET_MessageHeader,
464 GNUNET_MQ_handler_end ()
466 struct GNUNET_MessageHeader *test;
467 struct GNUNET_MQ_Envelope *env;
471 GNUNET_assert (GNUNET_NO == h->currently_up);
472 h->mq = GNUNET_CLIENT_connect (h->cfg,
479 LOG (GNUNET_ERROR_TYPE_DEBUG,
480 "GNUNET_CLIENT_connect returned NULL\n");
481 if (NULL != h->conn_status)
482 h->conn_status (h->conn_status_cls,
484 return GNUNET_SYSERR;
486 LOG (GNUNET_ERROR_TYPE_DEBUG,
487 "Sending TEST message to ARM\n");
488 env = GNUNET_MQ_msg (test,
489 GNUNET_MESSAGE_TYPE_ARM_TEST);
490 GNUNET_MQ_send (h->mq,
497 * Set up a context for communicating with ARM, then
498 * start connecting to the ARM service using that context.
500 * @param cfg configuration to use (needed to contact ARM;
501 * the ARM service may internally use a different
502 * configuration to determine how to start the service).
503 * @param conn_status will be called when connecting/disconnecting
504 * @param conn_status_cls closure for @a conn_status
505 * @return context to use for further ARM operations, NULL on error.
507 struct GNUNET_ARM_Handle *
508 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
509 GNUNET_ARM_ConnectionStatusCallback conn_status,
510 void *conn_status_cls)
512 struct GNUNET_ARM_Handle *h;
514 h = GNUNET_new (struct GNUNET_ARM_Handle);
516 h->conn_status = conn_status;
517 h->conn_status_cls = conn_status_cls;
518 if (GNUNET_OK != reconnect_arm (h))
528 * Disconnect from the ARM service (if connected) and destroy the context.
530 * @param h the handle that was being used
533 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
535 struct GNUNET_ARM_Operation *op;
537 LOG (GNUNET_ERROR_TYPE_DEBUG,
538 "Disconnecting from ARM service\n");
539 while (NULL != (op = h->operation_pending_head))
541 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
542 h->operation_pending_tail,
544 if (NULL != op->result_cont)
545 op->result_cont (op->cont_cls,
546 GNUNET_ARM_REQUEST_DISCONNECTED,
548 if (NULL != op->list_cont)
549 op->list_cont (op->cont_cls,
550 GNUNET_ARM_REQUEST_DISCONNECTED,
553 if (NULL != op->async)
555 GNUNET_SCHEDULER_cancel (op->async);
562 GNUNET_MQ_destroy (h->mq);
565 if (NULL != h->reconnect_task)
567 GNUNET_SCHEDULER_cancel (h->reconnect_task);
568 h->reconnect_task = NULL;
575 * A client specifically requested starting of ARM itself.
576 * Starts the ARM service.
578 * @param h the handle with configuration details
579 * @param std_inheritance inheritance of std streams
580 * @return operation status code
582 static enum GNUNET_ARM_Result
583 start_arm_service (struct GNUNET_ARM_Handle *h,
584 enum GNUNET_OS_InheritStdioFlags std_inheritance)
586 struct GNUNET_OS_Process *proc;
595 GNUNET_CONFIGURATION_get_value_string (h->cfg,
599 loprefix = GNUNET_strdup ("");
601 loprefix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
604 GNUNET_CONFIGURATION_get_value_string (h->cfg,
608 lopostfix = GNUNET_strdup ("");
610 lopostfix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
613 GNUNET_CONFIGURATION_get_value_string (h->cfg,
618 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
621 GNUNET_free (loprefix);
622 GNUNET_free (lopostfix);
623 return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
626 GNUNET_CONFIGURATION_get_value_filename (h->cfg,
631 binary = GNUNET_OS_get_libexec_binary_path (cbinary);
632 GNUNET_asprintf ("edbinary,
635 GNUNET_free (cbinary);
637 GNUNET_CONFIGURATION_have_value (h->cfg,
641 GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
645 GNUNET_CONFIGURATION_have_value (h->cfg,
649 /* Means we are ONLY running locally */
650 /* we're clearly running a test, don't daemonize */
652 proc = GNUNET_OS_start_process_s (GNUNET_NO,
657 /* no daemonization! */
661 proc = GNUNET_OS_start_process_s (GNUNET_NO,
667 /* no daemonization! */
674 proc = GNUNET_OS_start_process_s (GNUNET_NO,
679 "-d", /* do daemonize */
682 proc = GNUNET_OS_start_process_s (GNUNET_NO,
688 "-d", /* do daemonize */
692 GNUNET_free (binary);
693 GNUNET_free (quotedbinary);
694 GNUNET_free_non_null (config);
695 GNUNET_free (loprefix);
696 GNUNET_free (lopostfix);
698 return GNUNET_ARM_RESULT_START_FAILED;
699 GNUNET_OS_process_destroy (proc);
700 return GNUNET_ARM_RESULT_STARTING;
705 * Abort an operation. Only prevents the callback from being
706 * called, the operation may still complete.
708 * @param op operation to cancel
711 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
713 struct GNUNET_ARM_Handle *h = op->h;
717 op->result_cont = NULL;
720 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
721 h->operation_pending_tail,
728 * Start or stop a service.
730 * @param h handle to ARM
731 * @param service_name name of the service
732 * @param cb callback to invoke when service is ready
733 * @param cb_cls closure for @a cb
734 * @param type type of the request
735 * @return handle to queue, NULL on error
737 static struct GNUNET_ARM_Operation *
738 change_service (struct GNUNET_ARM_Handle *h,
739 const char *service_name,
740 GNUNET_ARM_ResultCallback cb,
744 struct GNUNET_ARM_Operation *op;
746 struct GNUNET_MQ_Envelope *env;
747 struct GNUNET_ARM_Message *msg;
749 slen = strlen (service_name) + 1;
750 if (slen + sizeof (struct GNUNET_ARM_Message) >=
751 GNUNET_MAX_MESSAGE_SIZE)
756 if (0 == h->request_id_counter)
757 h->request_id_counter++;
758 op = GNUNET_new (struct GNUNET_ARM_Operation);
760 op->result_cont = cb;
761 op->cont_cls = cb_cls;
762 op->id = h->request_id_counter++;
763 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
764 h->operation_pending_tail,
766 env = GNUNET_MQ_msg_extra (msg,
769 msg->reserved = htonl (0);
770 msg->request_id = GNUNET_htonll (op->id);
771 GNUNET_memcpy (&msg[1],
774 GNUNET_MQ_send (h->mq,
781 * Task run to notify application that ARM is already up.
783 * @param cls the operation that asked ARM to be started
786 notify_running (void *cls)
788 struct GNUNET_ARM_Operation *op = cls;
789 struct GNUNET_ARM_Handle *h = op->h;
792 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
793 h->operation_pending_tail,
795 if (NULL != op->result_cont)
796 op->result_cont (op->cont_cls,
797 GNUNET_ARM_REQUEST_SENT_OK,
798 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
799 if ( (GNUNET_YES == h->currently_up) &&
800 (NULL != h->conn_status) )
801 h->conn_status (h->conn_status_cls,
808 * Task run to notify application that ARM is being started.
810 * @param cls the operation that asked ARM to be started
813 notify_starting (void *cls)
815 struct GNUNET_ARM_Operation *op = cls;
816 struct GNUNET_ARM_Handle *h = op->h;
819 LOG (GNUNET_ERROR_TYPE_DEBUG,
820 "Notifying client that we started the ARM service\n");
821 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
822 h->operation_pending_tail,
824 if (NULL != op->result_cont)
825 op->result_cont (op->cont_cls,
826 GNUNET_ARM_REQUEST_SENT_OK,
833 * Request for a service to be started.
835 * @param h handle to ARM
836 * @param service_name name of the service
837 * @param std_inheritance inheritance of std streams
838 * @param cont callback to invoke after request is sent or not sent
839 * @param cont_cls closure for @a cont
840 * @return handle for the operation, NULL on error
842 struct GNUNET_ARM_Operation *
843 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
844 const char *service_name,
845 enum GNUNET_OS_InheritStdioFlags std_inheritance,
846 GNUNET_ARM_ResultCallback cont,
849 struct GNUNET_ARM_Operation *op;
850 enum GNUNET_ARM_Result ret;
852 LOG (GNUNET_ERROR_TYPE_DEBUG,
853 "Starting service `%s'\n",
855 if (0 != strcasecmp ("arm",
857 return change_service (h,
861 GNUNET_MESSAGE_TYPE_ARM_START);
864 * 1) We're connected to ARM already. Invoke the callback immediately.
865 * 2) We're not connected to ARM.
866 * Cancel any reconnection attempts temporarily, then perform
869 if (GNUNET_YES == h->currently_up)
871 LOG (GNUNET_ERROR_TYPE_DEBUG,
872 "ARM is already running\n");
873 op = GNUNET_new (struct GNUNET_ARM_Operation);
875 op->result_cont = cont;
876 op->cont_cls = cont_cls;
877 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
878 h->operation_pending_tail,
880 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
884 /* This is an inherently uncertain choice, as it is of course
885 theoretically possible that ARM is up and we just did not
886 yet complete the MQ handshake. However, given that users
887 are unlikely to hammer 'gnunet-arm -s' on a busy system,
888 the above check should catch 99.99% of the cases where ARM
889 is already running. */
890 LOG (GNUNET_ERROR_TYPE_DEBUG,
891 "Starting ARM service\n");
892 ret = start_arm_service (h,
894 if (GNUNET_ARM_RESULT_STARTING == ret)
896 op = GNUNET_new (struct GNUNET_ARM_Operation);
898 op->result_cont = cont;
899 op->cont_cls = cont_cls;
900 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
901 h->operation_pending_tail,
903 op->starting_ret = ret;
904 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
911 * Request a service to be stopped. Stopping arm itself will not
912 * invalidate its handle, and ARM API will try to restore connection
913 * to the ARM service, even if ARM connection was lost because you
914 * asked for ARM to be stopped. Call
915 * #GNUNET_ARM_disconnect() to free the handle and prevent
916 * further connection attempts.
918 * @param h handle to ARM
919 * @param service_name name of the service
920 * @param cont callback to invoke after request is sent or is not sent
921 * @param cont_cls closure for @a cont
922 * @return handle for the operation, NULL on error
924 struct GNUNET_ARM_Operation *
925 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
926 const char *service_name,
927 GNUNET_ARM_ResultCallback cont,
930 struct GNUNET_ARM_Operation *op;
932 LOG (GNUNET_ERROR_TYPE_DEBUG,
933 "Stopping service `%s'\n",
935 op = change_service (h,
939 GNUNET_MESSAGE_TYPE_ARM_STOP);
942 /* If the service is ARM, set a flag as we will use MQ errors
943 to detect that the process is really gone. */
944 if (0 == strcasecmp (service_name,
946 op->is_arm_stop = GNUNET_YES;
952 * Request a list of running services.
954 * @param h handle to ARM
955 * @param cont callback to invoke after request is sent or is not sent
956 * @param cont_cls closure for @a cont
957 * @return handle for the operation, NULL on error
959 struct GNUNET_ARM_Operation *
960 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
961 GNUNET_ARM_ServiceListCallback cont,
964 struct GNUNET_ARM_Operation *op;
965 struct GNUNET_MQ_Envelope *env;
966 struct GNUNET_ARM_Message *msg;
968 LOG (GNUNET_ERROR_TYPE_DEBUG,
969 "Requesting LIST from ARM service\n");
970 if (0 == h->request_id_counter)
971 h->request_id_counter++;
972 op = GNUNET_new (struct GNUNET_ARM_Operation);
974 op->list_cont = cont;
975 op->cont_cls = cont_cls;
976 op->id = h->request_id_counter++;
977 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
978 h->operation_pending_tail,
980 env = GNUNET_MQ_msg (msg,
981 GNUNET_MESSAGE_TYPE_ARM_LIST);
982 msg->reserved = htonl (0);
983 msg->request_id = GNUNET_htonll (op->id);
984 GNUNET_MQ_send (h->mq,
990 /* end of arm_api.c */