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 */
685 proc = GNUNET_OS_start_process_s (GNUNET_NO,
691 "-d", /* do daemonize */
695 GNUNET_free (binary);
696 GNUNET_free (quotedbinary);
697 GNUNET_free_non_null (config);
698 GNUNET_free (loprefix);
699 GNUNET_free (lopostfix);
701 return GNUNET_ARM_RESULT_START_FAILED;
702 GNUNET_OS_process_destroy (proc);
703 return GNUNET_ARM_RESULT_STARTING;
708 * Abort an operation. Only prevents the callback from being
709 * called, the operation may still complete.
711 * @param op operation to cancel
714 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
716 struct GNUNET_ARM_Handle *h = op->h;
720 op->result_cont = NULL;
723 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
724 h->operation_pending_tail,
731 * Start or stop a service.
733 * @param h handle to ARM
734 * @param service_name name of the service
735 * @param cb callback to invoke when service is ready
736 * @param cb_cls closure for @a cb
737 * @param type type of the request
738 * @return handle to queue, NULL on error
740 static struct GNUNET_ARM_Operation *
741 change_service (struct GNUNET_ARM_Handle *h,
742 const char *service_name,
743 GNUNET_ARM_ResultCallback cb,
747 struct GNUNET_ARM_Operation *op;
749 struct GNUNET_MQ_Envelope *env;
750 struct GNUNET_ARM_Message *msg;
752 slen = strlen (service_name) + 1;
753 if (slen + sizeof (struct GNUNET_ARM_Message) >=
754 GNUNET_MAX_MESSAGE_SIZE)
759 if (0 == h->request_id_counter)
760 h->request_id_counter++;
761 op = GNUNET_new (struct GNUNET_ARM_Operation);
763 op->result_cont = cb;
764 op->cont_cls = cb_cls;
765 op->id = h->request_id_counter++;
766 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
767 h->operation_pending_tail,
769 env = GNUNET_MQ_msg_extra (msg,
772 msg->reserved = htonl (0);
773 msg->request_id = GNUNET_htonll (op->id);
774 GNUNET_memcpy (&msg[1],
777 GNUNET_MQ_send (h->mq,
784 * Task run to notify application that ARM is already up.
786 * @param cls the operation that asked ARM to be started
789 notify_running (void *cls)
791 struct GNUNET_ARM_Operation *op = cls;
792 struct GNUNET_ARM_Handle *h = op->h;
795 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
796 h->operation_pending_tail,
798 if (NULL != op->result_cont)
799 op->result_cont (op->cont_cls,
800 GNUNET_ARM_REQUEST_SENT_OK,
801 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
802 if ( (GNUNET_YES == h->currently_up) &&
803 (NULL != h->conn_status) )
804 h->conn_status (h->conn_status_cls,
811 * Task run to notify application that ARM is being started.
813 * @param cls the operation that asked ARM to be started
816 notify_starting (void *cls)
818 struct GNUNET_ARM_Operation *op = cls;
819 struct GNUNET_ARM_Handle *h = op->h;
822 LOG (GNUNET_ERROR_TYPE_DEBUG,
823 "Notifying client that we started the ARM service\n");
824 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
825 h->operation_pending_tail,
827 if (NULL != op->result_cont)
828 op->result_cont (op->cont_cls,
829 GNUNET_ARM_REQUEST_SENT_OK,
836 * Request for a service to be started.
838 * @param h handle to ARM
839 * @param service_name name of the service
840 * @param std_inheritance inheritance of std streams
841 * @param cont callback to invoke after request is sent or not sent
842 * @param cont_cls closure for @a cont
843 * @return handle for the operation, NULL on error
845 struct GNUNET_ARM_Operation *
846 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
847 const char *service_name,
848 enum GNUNET_OS_InheritStdioFlags std_inheritance,
849 GNUNET_ARM_ResultCallback cont,
852 struct GNUNET_ARM_Operation *op;
853 enum GNUNET_ARM_Result ret;
855 LOG (GNUNET_ERROR_TYPE_DEBUG,
856 "Starting service `%s'\n",
858 if (0 != strcasecmp ("arm",
860 return change_service (h,
864 GNUNET_MESSAGE_TYPE_ARM_START);
867 * 1) We're connected to ARM already. Invoke the callback immediately.
868 * 2) We're not connected to ARM.
869 * Cancel any reconnection attempts temporarily, then perform
872 if (GNUNET_YES == h->currently_up)
874 LOG (GNUNET_ERROR_TYPE_DEBUG,
875 "ARM is already running\n");
876 op = GNUNET_new (struct GNUNET_ARM_Operation);
878 op->result_cont = cont;
879 op->cont_cls = cont_cls;
880 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
881 h->operation_pending_tail,
883 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
887 /* This is an inherently uncertain choice, as it is of course
888 theoretically possible that ARM is up and we just did not
889 yet complete the MQ handshake. However, given that users
890 are unlikely to hammer 'gnunet-arm -s' on a busy system,
891 the above check should catch 99.99% of the cases where ARM
892 is already running. */
893 LOG (GNUNET_ERROR_TYPE_DEBUG,
894 "Starting ARM service\n");
895 ret = start_arm_service (h,
897 if (GNUNET_ARM_RESULT_STARTING == ret)
899 op = GNUNET_new (struct GNUNET_ARM_Operation);
901 op->result_cont = cont;
902 op->cont_cls = cont_cls;
903 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
904 h->operation_pending_tail,
906 op->starting_ret = ret;
907 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
914 * Request a service to be stopped. Stopping arm itself will not
915 * invalidate its handle, and ARM API will try to restore connection
916 * to the ARM service, even if ARM connection was lost because you
917 * asked for ARM to be stopped. Call
918 * #GNUNET_ARM_disconnect() to free the handle and prevent
919 * further connection attempts.
921 * @param h handle to ARM
922 * @param service_name name of the service
923 * @param cont callback to invoke after request is sent or is not sent
924 * @param cont_cls closure for @a cont
925 * @return handle for the operation, NULL on error
927 struct GNUNET_ARM_Operation *
928 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
929 const char *service_name,
930 GNUNET_ARM_ResultCallback cont,
933 struct GNUNET_ARM_Operation *op;
935 LOG (GNUNET_ERROR_TYPE_DEBUG,
936 "Stopping service `%s'\n",
938 op = change_service (h,
942 GNUNET_MESSAGE_TYPE_ARM_STOP);
945 /* If the service is ARM, set a flag as we will use MQ errors
946 to detect that the process is really gone. */
947 if (0 == strcasecmp (service_name,
949 op->is_arm_stop = GNUNET_YES;
955 * Request a list of running services.
957 * @param h handle to ARM
958 * @param cont callback to invoke after request is sent or is not sent
959 * @param cont_cls closure for @a cont
960 * @return handle for the operation, NULL on error
962 struct GNUNET_ARM_Operation *
963 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
964 GNUNET_ARM_ServiceListCallback cont,
967 struct GNUNET_ARM_Operation *op;
968 struct GNUNET_MQ_Envelope *env;
969 struct GNUNET_ARM_Message *msg;
971 LOG (GNUNET_ERROR_TYPE_DEBUG,
972 "Requesting LIST from ARM service\n");
973 if (0 == h->request_id_counter)
974 h->request_id_counter++;
975 op = GNUNET_new (struct GNUNET_ARM_Operation);
977 op->list_cont = cont;
978 op->cont_cls = cont_cls;
979 op->id = h->request_id_counter++;
980 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
981 h->operation_pending_tail,
983 env = GNUNET_MQ_msg (msg,
984 GNUNET_MESSAGE_TYPE_ARM_LIST);
985 msg->reserved = htonl (0);
986 msg->request_id = GNUNET_htonll (op->id);
987 GNUNET_MQ_send (h->mq,
993 /* end of arm_api.c */