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 Affero 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.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * @brief API for accessing the ARM service
22 * @author Christian Grothoff
26 #include "gnunet_util_lib.h"
27 #include "gnunet_arm_service.h"
28 #include "gnunet_protocols.h"
31 #define LOG(kind,...) GNUNET_log_from (kind, "arm-api",__VA_ARGS__)
35 * Entry in a doubly-linked list of operations awaiting for replies
36 * (in-order) from the ARM service.
38 struct GNUNET_ARM_Operation
41 * This is a doubly-linked list.
43 struct GNUNET_ARM_Operation *next;
46 * This is a doubly-linked list.
48 struct GNUNET_ARM_Operation *prev;
53 struct GNUNET_ARM_Handle *h;
56 * Callback for service state change requests.
58 GNUNET_ARM_ResultCallback result_cont;
61 * Callback for service list requests.
63 GNUNET_ARM_ServiceListCallback list_cont;
66 * Closure for @e result_cont or @e list_cont.
71 * Task for async completion.
73 struct GNUNET_SCHEDULER_Task *async;
76 * Unique ID for the request.
81 * Result of this operation for #notify_starting().
83 enum GNUNET_ARM_Result starting_ret;
86 * Is this an operation to stop the ARM service?
93 * Handle for interacting with ARM.
95 struct GNUNET_ARM_Handle
98 * Our connection to the ARM service.
100 struct GNUNET_MQ_Handle *mq;
103 * The configuration that we are using.
105 const struct GNUNET_CONFIGURATION_Handle *cfg;
108 * Head of doubly-linked list of pending operations.
110 struct GNUNET_ARM_Operation *operation_pending_head;
113 * Tail of doubly-linked list of pending operations.
115 struct GNUNET_ARM_Operation *operation_pending_tail;
118 * Callback to invoke on connection/disconnection.
120 GNUNET_ARM_ConnectionStatusCallback conn_status;
123 * Closure for @e conn_status.
125 void *conn_status_cls;
128 * ARM operation where the goal is to wait for ARM shutdown to
129 * complete. This operation is special in that it waits for an
130 * error on the @e mq. So we complete it by calling the
131 * continuation in the #mq_error_handler(). Note that the operation
132 * is no longer in the @e operation_pending_head DLL once it is
133 * referenced from this field.
135 struct GNUNET_ARM_Operation *thm;
138 * ID of the reconnect task (if any).
140 struct GNUNET_SCHEDULER_Task *reconnect_task;
143 * Current delay we use for re-trying to connect to core.
145 struct GNUNET_TIME_Relative retry_backoff;
148 * Counter for request identifiers. They are used to match replies
149 * from ARM to operations in the @e operation_pending_head DLL.
151 uint64_t request_id_counter;
154 * Have we detected that ARM is up?
164 * @param h arm handle
165 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
168 reconnect_arm (struct GNUNET_ARM_Handle *h);
172 * Task scheduled to try to re-connect to arm.
174 * @param cls the `struct GNUNET_ARM_Handle`
177 reconnect_arm_task (void *cls)
179 struct GNUNET_ARM_Handle *h = cls;
181 h->reconnect_task = NULL;
187 * Close down any existing connection to the ARM service and
188 * try re-establishing it later.
190 * @param h our handle
193 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
195 struct GNUNET_ARM_Operation *op;
199 GNUNET_MQ_destroy (h->mq);
202 h->currently_up = GNUNET_NO;
203 GNUNET_assert (NULL == h->reconnect_task);
205 GNUNET_SCHEDULER_add_delayed (h->retry_backoff,
208 while (NULL != (op = h->operation_pending_head))
210 if (NULL != op->result_cont)
211 op->result_cont (op->cont_cls,
212 GNUNET_ARM_REQUEST_DISCONNECTED,
214 if (NULL != op->list_cont)
215 op->list_cont (op->cont_cls,
216 GNUNET_ARM_REQUEST_DISCONNECTED,
219 GNUNET_ARM_operation_cancel (op);
221 GNUNET_assert (NULL == h->operation_pending_head);
222 h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
223 if (NULL != h->conn_status)
224 h->conn_status (h->conn_status_cls,
230 * Find a control message by its unique ID.
232 * @param h ARM handle
233 * @param id unique message ID to use for the lookup
234 * @return NULL if not found
236 static struct GNUNET_ARM_Operation *
237 find_op_by_id (struct GNUNET_ARM_Handle *h,
240 struct GNUNET_ARM_Operation *result;
242 for (result = h->operation_pending_head; NULL != result; result = result->next)
243 if (id == result->id)
250 * Handler for ARM replies.
252 * @param cls our `struct GNUNET_ARM_Handle`
253 * @param res the message received from the arm service
256 handle_arm_result (void *cls,
257 const struct GNUNET_ARM_ResultMessage *res)
259 struct GNUNET_ARM_Handle *h = cls;
260 struct GNUNET_ARM_Operation *op;
262 enum GNUNET_ARM_Result result;
263 GNUNET_ARM_ResultCallback result_cont;
264 void *result_cont_cls;
266 id = GNUNET_ntohll (res->arm_msg.request_id);
267 op = find_op_by_id (h,
271 LOG (GNUNET_ERROR_TYPE_DEBUG,
272 "Message with unknown id %llu\n",
273 (unsigned long long) id);
277 result = (enum GNUNET_ARM_Result) ntohl (res->result);
278 if ( (GNUNET_YES == op->is_arm_stop) &&
279 (GNUNET_ARM_RESULT_STOPPING == result) )
281 /* special case: if we are stopping 'gnunet-service-arm', we do not just
282 wait for the result message, but also wait for the service to close
283 the connection (and then we have to close our client handle as well);
284 this is done by installing a different receive handler, waiting for
285 the connection to go down */
289 op->result_cont (h->thm->cont_cls,
290 GNUNET_ARM_REQUEST_SENT_OK,
291 GNUNET_ARM_RESULT_IS_NOT_KNOWN);
292 GNUNET_free (h->thm);
294 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
295 h->operation_pending_tail,
300 result_cont = op->result_cont;
301 result_cont_cls = op->cont_cls;
302 GNUNET_ARM_operation_cancel (op);
303 if (NULL != result_cont)
304 result_cont (result_cont_cls,
305 GNUNET_ARM_REQUEST_SENT_OK,
311 * Checked that list result message is well-formed.
313 * @param cls our `struct GNUNET_ARM_Handle`
314 * @param lres the message received from the arm service
315 * @return #GNUNET_OK if message is well-formed
318 check_arm_list_result (void *cls,
319 const struct GNUNET_ARM_ListResultMessage *lres)
321 const char *pos = (const char *) &lres[1];
322 uint16_t rcount = ntohs (lres->count);
323 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
327 for (unsigned int i = 0; i < rcount; i++)
329 const char *end = memchr (pos, 0, msize - size_check);
333 return GNUNET_SYSERR;
335 size_check += (end - pos) + 1;
343 * Handler for ARM list replies.
345 * @param cls our `struct GNUNET_ARM_Handle`
346 * @param lres the message received from the arm service
349 handle_arm_list_result (void *cls,
350 const struct GNUNET_ARM_ListResultMessage *lres)
352 struct GNUNET_ARM_Handle *h = cls;
353 uint16_t rcount = ntohs (lres->count);
354 const char *list[rcount];
355 const char *pos = (const char *) &lres[1];
356 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
357 struct GNUNET_ARM_Operation *op;
361 id = GNUNET_ntohll (lres->arm_msg.request_id);
362 op = find_op_by_id (h,
366 LOG (GNUNET_ERROR_TYPE_DEBUG,
367 "Message with unknown id %llu\n",
368 (unsigned long long) id);
372 for (unsigned int i = 0; i < rcount; i++)
374 const char *end = memchr (pos,
378 /* Assert, as this was already checked in #check_arm_list_result() */
379 GNUNET_assert (NULL != end);
381 size_check += (end - pos) + 1;
384 if (NULL != op->list_cont)
385 op->list_cont (op->cont_cls,
386 GNUNET_ARM_REQUEST_SENT_OK,
389 GNUNET_ARM_operation_cancel (op);
394 * Receive confirmation from test, ARM service is up.
396 * @param cls closure with the `struct GNUNET_ARM_Handle`
397 * @param msg message received
400 handle_confirm (void *cls,
401 const struct GNUNET_MessageHeader *msg)
403 struct GNUNET_ARM_Handle *h = cls;
405 LOG (GNUNET_ERROR_TYPE_DEBUG,
406 "Got confirmation from ARM that we are up!\n");
407 if (GNUNET_NO == h->currently_up)
409 h->currently_up = GNUNET_YES;
410 if (NULL != h->conn_status)
411 h->conn_status (h->conn_status_cls,
418 * Generic error handler, called with the appropriate error code and
419 * the same closure specified at the creation of the message queue.
420 * Not every message queue implementation supports an error handler.
422 * @param cls closure with the `struct GNUNET_ARM_Handle *`
423 * @param error error code
426 mq_error_handler (void *cls,
427 enum GNUNET_MQ_Error error)
429 struct GNUNET_ARM_Handle *h = cls;
430 struct GNUNET_ARM_Operation *op;
432 h->currently_up = GNUNET_NO;
433 if (NULL != (op = h->thm))
436 op->result_cont (op->cont_cls,
437 GNUNET_ARM_REQUEST_SENT_OK,
438 GNUNET_ARM_RESULT_STOPPED);
441 reconnect_arm_later (h);
448 * @param h arm handle
449 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
452 reconnect_arm (struct GNUNET_ARM_Handle *h)
454 struct GNUNET_MQ_MessageHandler handlers[] = {
455 GNUNET_MQ_hd_fixed_size (arm_result,
456 GNUNET_MESSAGE_TYPE_ARM_RESULT,
457 struct GNUNET_ARM_ResultMessage,
459 GNUNET_MQ_hd_var_size (arm_list_result,
460 GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT,
461 struct GNUNET_ARM_ListResultMessage,
463 GNUNET_MQ_hd_fixed_size (confirm,
464 GNUNET_MESSAGE_TYPE_ARM_TEST,
465 struct GNUNET_MessageHeader,
467 GNUNET_MQ_handler_end ()
469 struct GNUNET_MessageHeader *test;
470 struct GNUNET_MQ_Envelope *env;
474 GNUNET_assert (GNUNET_NO == h->currently_up);
475 h->mq = GNUNET_CLIENT_connect (h->cfg,
482 LOG (GNUNET_ERROR_TYPE_DEBUG,
483 "GNUNET_CLIENT_connect returned NULL\n");
484 if (NULL != h->conn_status)
485 h->conn_status (h->conn_status_cls,
487 return GNUNET_SYSERR;
489 LOG (GNUNET_ERROR_TYPE_DEBUG,
490 "Sending TEST message to ARM\n");
491 env = GNUNET_MQ_msg (test,
492 GNUNET_MESSAGE_TYPE_ARM_TEST);
493 GNUNET_MQ_send (h->mq,
500 * Set up a context for communicating with ARM, then
501 * start connecting to the ARM service using that context.
503 * @param cfg configuration to use (needed to contact ARM;
504 * the ARM service may internally use a different
505 * configuration to determine how to start the service).
506 * @param conn_status will be called when connecting/disconnecting
507 * @param conn_status_cls closure for @a conn_status
508 * @return context to use for further ARM operations, NULL on error.
510 struct GNUNET_ARM_Handle *
511 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
512 GNUNET_ARM_ConnectionStatusCallback conn_status,
513 void *conn_status_cls)
515 struct GNUNET_ARM_Handle *h;
517 h = GNUNET_new (struct GNUNET_ARM_Handle);
519 h->conn_status = conn_status;
520 h->conn_status_cls = conn_status_cls;
521 if (GNUNET_OK != reconnect_arm (h))
531 * Disconnect from the ARM service (if connected) and destroy the context.
533 * @param h the handle that was being used
536 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
538 struct GNUNET_ARM_Operation *op;
540 LOG (GNUNET_ERROR_TYPE_DEBUG,
541 "Disconnecting from ARM service\n");
542 while (NULL != (op = h->operation_pending_head))
544 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
545 h->operation_pending_tail,
547 if (NULL != op->result_cont)
548 op->result_cont (op->cont_cls,
549 GNUNET_ARM_REQUEST_DISCONNECTED,
551 if (NULL != op->list_cont)
552 op->list_cont (op->cont_cls,
553 GNUNET_ARM_REQUEST_DISCONNECTED,
556 if (NULL != op->async)
558 GNUNET_SCHEDULER_cancel (op->async);
565 GNUNET_MQ_destroy (h->mq);
568 if (NULL != h->reconnect_task)
570 GNUNET_SCHEDULER_cancel (h->reconnect_task);
571 h->reconnect_task = NULL;
578 * A client specifically requested starting of ARM itself.
579 * Starts the ARM service.
581 * @param h the handle with configuration details
582 * @param std_inheritance inheritance of std streams
583 * @return operation status code
585 static enum GNUNET_ARM_Result
586 start_arm_service (struct GNUNET_ARM_Handle *h,
587 enum GNUNET_OS_InheritStdioFlags std_inheritance)
589 struct GNUNET_OS_Process *proc;
598 GNUNET_CONFIGURATION_get_value_string (h->cfg,
602 loprefix = GNUNET_strdup ("");
604 loprefix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
607 GNUNET_CONFIGURATION_get_value_string (h->cfg,
611 lopostfix = GNUNET_strdup ("");
613 lopostfix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
616 GNUNET_CONFIGURATION_get_value_string (h->cfg,
621 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
624 GNUNET_free (loprefix);
625 GNUNET_free (lopostfix);
626 return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
629 GNUNET_CONFIGURATION_get_value_filename (h->cfg,
634 binary = GNUNET_OS_get_libexec_binary_path (cbinary);
635 GNUNET_asprintf ("edbinary,
638 GNUNET_free (cbinary);
640 GNUNET_CONFIGURATION_have_value (h->cfg,
644 GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
648 GNUNET_CONFIGURATION_have_value (h->cfg,
652 /* Means we are ONLY running locally */
653 /* we're clearly running a test, don't daemonize */
655 proc = GNUNET_OS_start_process_s (GNUNET_NO,
660 /* no daemonization! */
664 proc = GNUNET_OS_start_process_s (GNUNET_NO,
670 /* no daemonization! */
677 proc = GNUNET_OS_start_process_s (GNUNET_NO,
682 "-d", /* do daemonize */
686 proc = GNUNET_OS_start_process_s (GNUNET_NO,
692 "-d", /* do daemonize */
696 GNUNET_free (binary);
697 GNUNET_free (quotedbinary);
698 GNUNET_free_non_null (config);
699 GNUNET_free (loprefix);
700 GNUNET_free (lopostfix);
702 return GNUNET_ARM_RESULT_START_FAILED;
703 GNUNET_OS_process_destroy (proc);
704 return GNUNET_ARM_RESULT_STARTING;
709 * Abort an operation. Only prevents the callback from being
710 * called, the operation may still complete.
712 * @param op operation to cancel
715 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
717 struct GNUNET_ARM_Handle *h = op->h;
721 op->result_cont = NULL;
724 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
725 h->operation_pending_tail,
732 * Start or stop a service.
734 * @param h handle to ARM
735 * @param service_name name of the service
736 * @param cb callback to invoke when service is ready
737 * @param cb_cls closure for @a cb
738 * @param type type of the request
739 * @return handle to queue, NULL on error
741 static struct GNUNET_ARM_Operation *
742 change_service (struct GNUNET_ARM_Handle *h,
743 const char *service_name,
744 GNUNET_ARM_ResultCallback cb,
748 struct GNUNET_ARM_Operation *op;
750 struct GNUNET_MQ_Envelope *env;
751 struct GNUNET_ARM_Message *msg;
753 slen = strlen (service_name) + 1;
754 if (slen + sizeof (struct GNUNET_ARM_Message) >=
755 GNUNET_MAX_MESSAGE_SIZE)
760 if (0 == h->request_id_counter)
761 h->request_id_counter++;
762 op = GNUNET_new (struct GNUNET_ARM_Operation);
764 op->result_cont = cb;
765 op->cont_cls = cb_cls;
766 op->id = h->request_id_counter++;
767 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
768 h->operation_pending_tail,
770 env = GNUNET_MQ_msg_extra (msg,
773 msg->reserved = htonl (0);
774 msg->request_id = GNUNET_htonll (op->id);
775 GNUNET_memcpy (&msg[1],
778 GNUNET_MQ_send (h->mq,
785 * Task run to notify application that ARM is already up.
787 * @param cls the operation that asked ARM to be started
790 notify_running (void *cls)
792 struct GNUNET_ARM_Operation *op = cls;
793 struct GNUNET_ARM_Handle *h = op->h;
796 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
797 h->operation_pending_tail,
799 if (NULL != op->result_cont)
800 op->result_cont (op->cont_cls,
801 GNUNET_ARM_REQUEST_SENT_OK,
802 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
803 if ( (GNUNET_YES == h->currently_up) &&
804 (NULL != h->conn_status) )
805 h->conn_status (h->conn_status_cls,
812 * Task run to notify application that ARM is being started.
814 * @param cls the operation that asked ARM to be started
817 notify_starting (void *cls)
819 struct GNUNET_ARM_Operation *op = cls;
820 struct GNUNET_ARM_Handle *h = op->h;
823 LOG (GNUNET_ERROR_TYPE_DEBUG,
824 "Notifying client that we started the ARM service\n");
825 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
826 h->operation_pending_tail,
828 if (NULL != op->result_cont)
829 op->result_cont (op->cont_cls,
830 GNUNET_ARM_REQUEST_SENT_OK,
837 * Request for a service to be started.
839 * @param h handle to ARM
840 * @param service_name name of the service
841 * @param std_inheritance inheritance of std streams
842 * @param cont callback to invoke after request is sent or not sent
843 * @param cont_cls closure for @a cont
844 * @return handle for the operation, NULL on error
846 struct GNUNET_ARM_Operation *
847 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
848 const char *service_name,
849 enum GNUNET_OS_InheritStdioFlags std_inheritance,
850 GNUNET_ARM_ResultCallback cont,
853 struct GNUNET_ARM_Operation *op;
854 enum GNUNET_ARM_Result ret;
856 LOG (GNUNET_ERROR_TYPE_DEBUG,
857 "Starting service `%s'\n",
859 if (0 != strcasecmp ("arm",
861 return change_service (h,
865 GNUNET_MESSAGE_TYPE_ARM_START);
868 * 1) We're connected to ARM already. Invoke the callback immediately.
869 * 2) We're not connected to ARM.
870 * Cancel any reconnection attempts temporarily, then perform
873 if (GNUNET_YES == h->currently_up)
875 LOG (GNUNET_ERROR_TYPE_DEBUG,
876 "ARM is already running\n");
877 op = GNUNET_new (struct GNUNET_ARM_Operation);
879 op->result_cont = cont;
880 op->cont_cls = cont_cls;
881 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
882 h->operation_pending_tail,
884 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
888 /* This is an inherently uncertain choice, as it is of course
889 theoretically possible that ARM is up and we just did not
890 yet complete the MQ handshake. However, given that users
891 are unlikely to hammer 'gnunet-arm -s' on a busy system,
892 the above check should catch 99.99% of the cases where ARM
893 is already running. */
894 LOG (GNUNET_ERROR_TYPE_DEBUG,
895 "Starting ARM service\n");
896 ret = start_arm_service (h,
898 if (GNUNET_ARM_RESULT_STARTING == ret)
900 op = GNUNET_new (struct GNUNET_ARM_Operation);
902 op->result_cont = cont;
903 op->cont_cls = cont_cls;
904 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
905 h->operation_pending_tail,
907 op->starting_ret = ret;
908 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
915 * Request a service to be stopped. Stopping arm itself will not
916 * invalidate its handle, and ARM API will try to restore connection
917 * to the ARM service, even if ARM connection was lost because you
918 * asked for ARM to be stopped. Call
919 * #GNUNET_ARM_disconnect() to free the handle and prevent
920 * further connection attempts.
922 * @param h handle to ARM
923 * @param service_name name of the service
924 * @param cont callback to invoke after request is sent or is not sent
925 * @param cont_cls closure for @a cont
926 * @return handle for the operation, NULL on error
928 struct GNUNET_ARM_Operation *
929 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
930 const char *service_name,
931 GNUNET_ARM_ResultCallback cont,
934 struct GNUNET_ARM_Operation *op;
936 LOG (GNUNET_ERROR_TYPE_DEBUG,
937 "Stopping service `%s'\n",
939 op = change_service (h,
943 GNUNET_MESSAGE_TYPE_ARM_STOP);
946 /* If the service is ARM, set a flag as we will use MQ errors
947 to detect that the process is really gone. */
948 if (0 == strcasecmp (service_name,
950 op->is_arm_stop = GNUNET_YES;
956 * Request a list of running services.
958 * @param h handle to ARM
959 * @param cont callback to invoke after request is sent or is not sent
960 * @param cont_cls closure for @a cont
961 * @return handle for the operation, NULL on error
963 struct GNUNET_ARM_Operation *
964 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
965 GNUNET_ARM_ServiceListCallback cont,
968 struct GNUNET_ARM_Operation *op;
969 struct GNUNET_MQ_Envelope *env;
970 struct GNUNET_ARM_Message *msg;
972 LOG (GNUNET_ERROR_TYPE_DEBUG,
973 "Requesting LIST from ARM service\n");
974 if (0 == h->request_id_counter)
975 h->request_id_counter++;
976 op = GNUNET_new (struct GNUNET_ARM_Operation);
978 op->list_cont = cont;
979 op->cont_cls = cont_cls;
980 op->id = h->request_id_counter++;
981 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
982 h->operation_pending_tail,
984 env = GNUNET_MQ_msg (msg,
985 GNUNET_MESSAGE_TYPE_ARM_LIST);
986 msg->reserved = htonl (0);
987 msg->request_id = GNUNET_htonll (op->id);
988 GNUNET_MQ_send (h->mq,
994 /* end of arm_api.c */