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
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 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 General Public License for more details.
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., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
23 * @brief API for accessing the ARM service
24 * @author Christian Grothoff
28 #include "gnunet_util_lib.h"
29 #include "gnunet_arm_service.h"
30 #include "gnunet_protocols.h"
33 #define LOG(kind,...) GNUNET_log_from (kind, "arm-api",__VA_ARGS__)
37 * Entry in a doubly-linked list of operations awaiting for replies
38 * (in-order) from the ARM service.
40 struct GNUNET_ARM_Operation
43 * This is a doubly-linked list.
45 struct GNUNET_ARM_Operation *next;
48 * This is a doubly-linked list.
50 struct GNUNET_ARM_Operation *prev;
55 struct GNUNET_ARM_Handle *h;
58 * Callback for service state change requests.
60 GNUNET_ARM_ResultCallback result_cont;
63 * Callback for service list requests.
65 GNUNET_ARM_ServiceListCallback list_cont;
68 * Closure for @e result_cont or @e list_cont.
73 * Task for async completion.
75 struct GNUNET_SCHEDULER_Task *async;
78 * Unique ID for the request.
83 * Result of this operation for #notify_starting().
85 enum GNUNET_ARM_Result starting_ret;
88 * Is this an operation to stop the ARM service?
95 * Handle for interacting with ARM.
97 struct GNUNET_ARM_Handle
100 * Our connection to the ARM service.
102 struct GNUNET_MQ_Handle *mq;
105 * The configuration that we are using.
107 const struct GNUNET_CONFIGURATION_Handle *cfg;
110 * Head of doubly-linked list of pending operations.
112 struct GNUNET_ARM_Operation *operation_pending_head;
115 * Tail of doubly-linked list of pending operations.
117 struct GNUNET_ARM_Operation *operation_pending_tail;
120 * Callback to invoke on connection/disconnection.
122 GNUNET_ARM_ConnectionStatusCallback conn_status;
125 * Closure for @e conn_status.
127 void *conn_status_cls;
130 * ARM operation where the goal is to wait for ARM shutdown to
131 * complete. This operation is special in that it waits for an
132 * error on the @e mq. So we complete it by calling the
133 * continuation in the #mq_error_handler(). Note that the operation
134 * is no longer in the @e operation_pending_head DLL once it is
135 * referenced from this field.
137 struct GNUNET_ARM_Operation *thm;
140 * ID of the reconnect task (if any).
142 struct GNUNET_SCHEDULER_Task *reconnect_task;
145 * Current delay we use for re-trying to connect to core.
147 struct GNUNET_TIME_Relative retry_backoff;
150 * Counter for request identifiers. They are used to match replies
151 * from ARM to operations in the @e operation_pending_head DLL.
153 uint64_t request_id_counter;
156 * Have we detected that ARM is up?
166 * @param h arm handle
167 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
170 reconnect_arm (struct GNUNET_ARM_Handle *h);
174 * Task scheduled to try to re-connect to arm.
176 * @param cls the `struct GNUNET_ARM_Handle`
179 reconnect_arm_task (void *cls)
181 struct GNUNET_ARM_Handle *h = cls;
183 h->reconnect_task = NULL;
189 * Close down any existing connection to the ARM service and
190 * try re-establishing it later.
192 * @param h our handle
195 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
197 struct GNUNET_ARM_Operation *op;
201 GNUNET_MQ_destroy (h->mq);
204 h->currently_up = GNUNET_NO;
205 GNUNET_assert (NULL == h->reconnect_task);
207 GNUNET_SCHEDULER_add_delayed (h->retry_backoff,
210 while (NULL != (op = h->operation_pending_head))
212 if (NULL != op->result_cont)
213 op->result_cont (op->cont_cls,
214 GNUNET_ARM_REQUEST_DISCONNECTED,
216 if (NULL != op->list_cont)
217 op->list_cont (op->cont_cls,
218 GNUNET_ARM_REQUEST_DISCONNECTED,
221 GNUNET_ARM_operation_cancel (op);
223 GNUNET_assert (NULL == h->operation_pending_head);
224 h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
225 if (NULL != h->conn_status)
226 h->conn_status (h->conn_status_cls,
232 * Find a control message by its unique ID.
234 * @param h ARM handle
235 * @param id unique message ID to use for the lookup
236 * @return NULL if not found
238 static struct GNUNET_ARM_Operation *
239 find_op_by_id (struct GNUNET_ARM_Handle *h,
242 struct GNUNET_ARM_Operation *result;
244 for (result = h->operation_pending_head; NULL != result; result = result->next)
245 if (id == result->id)
252 * Handler for ARM replies.
254 * @param cls our `struct GNUNET_ARM_Handle`
255 * @param res the message received from the arm service
258 handle_arm_result (void *cls,
259 const struct GNUNET_ARM_ResultMessage *res)
261 struct GNUNET_ARM_Handle *h = cls;
262 struct GNUNET_ARM_Operation *op;
264 enum GNUNET_ARM_Result result;
265 GNUNET_ARM_ResultCallback result_cont;
266 void *result_cont_cls;
268 id = GNUNET_ntohll (res->arm_msg.request_id);
269 op = find_op_by_id (h,
273 LOG (GNUNET_ERROR_TYPE_DEBUG,
274 "Message with unknown id %llu\n",
275 (unsigned long long) id);
279 result = (enum GNUNET_ARM_Result) ntohl (res->result);
280 if ( (GNUNET_YES == op->is_arm_stop) &&
281 (GNUNET_ARM_RESULT_STOPPING == result) )
283 /* special case: if we are stopping 'gnunet-service-arm', we do not just
284 wait for the result message, but also wait for the service to close
285 the connection (and then we have to close our client handle as well);
286 this is done by installing a different receive handler, waiting for
287 the connection to go down */
291 op->result_cont (h->thm->cont_cls,
292 GNUNET_ARM_REQUEST_SENT_OK,
293 GNUNET_ARM_RESULT_IS_NOT_KNOWN);
294 GNUNET_free (h->thm);
296 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
297 h->operation_pending_tail,
302 result_cont = op->result_cont;
303 result_cont_cls = op->cont_cls;
304 GNUNET_ARM_operation_cancel (op);
305 if (NULL != result_cont)
306 result_cont (result_cont_cls,
307 GNUNET_ARM_REQUEST_SENT_OK,
313 * Checked that list result message is well-formed.
315 * @param cls our `struct GNUNET_ARM_Handle`
316 * @param lres the message received from the arm service
317 * @return #GNUNET_OK if message is well-formed
320 check_arm_list_result (void *cls,
321 const struct GNUNET_ARM_ListResultMessage *lres)
323 const char *pos = (const char *) &lres[1];
324 uint16_t rcount = ntohs (lres->count);
325 uint16_t msize = ntohs (lres->arm_msg.header.size);
329 for (unsigned int i = 0; i < rcount; i++)
331 const char *end = memchr (pos, 0, msize - size_check);
335 return GNUNET_SYSERR;
337 size_check += (end - pos) + 1;
345 * Handler for ARM list replies.
347 * @param cls our `struct GNUNET_ARM_Handle`
348 * @param lres the message received from the arm service
351 handle_arm_list_result (void *cls,
352 const struct GNUNET_ARM_ListResultMessage *lres)
354 struct GNUNET_ARM_Handle *h = cls;
355 uint16_t rcount = ntohs (lres->count);
356 const char *list[rcount];
357 const char *pos = (const char *) &lres[1];
358 uint16_t msize = ntohs (lres->arm_msg.header.size);
359 struct GNUNET_ARM_Operation *op;
363 id = GNUNET_ntohll (lres->arm_msg.request_id);
364 op = find_op_by_id (h,
368 LOG (GNUNET_ERROR_TYPE_DEBUG,
369 "Message with unknown id %llu\n",
370 (unsigned long long) id);
374 for (unsigned int i = 0; i < rcount; i++)
376 const char *end = memchr (pos,
380 /* Assert, as this was already checked in #check_arm_list_result() */
381 GNUNET_assert (NULL != end);
383 size_check += (end - pos) + 1;
386 if (NULL != op->list_cont)
387 op->list_cont (op->cont_cls,
388 GNUNET_ARM_REQUEST_SENT_OK,
391 GNUNET_ARM_operation_cancel (op);
396 * Receive confirmation from test, ARM service is up.
398 * @param cls closure with the `struct GNUNET_ARM_Handle`
399 * @param msg message received
402 handle_confirm (void *cls,
403 const struct GNUNET_MessageHeader *msg)
405 struct GNUNET_ARM_Handle *h = cls;
407 LOG (GNUNET_ERROR_TYPE_DEBUG,
408 "Got confirmation from ARM that we are up!\n");
409 if (GNUNET_NO == h->currently_up)
411 h->currently_up = GNUNET_YES;
412 if (NULL != h->conn_status)
413 h->conn_status (h->conn_status_cls,
420 * Generic error handler, called with the appropriate error code and
421 * the same closure specified at the creation of the message queue.
422 * Not every message queue implementation supports an error handler.
424 * @param cls closure with the `struct GNUNET_ARM_Handle *`
425 * @param error error code
428 mq_error_handler (void *cls,
429 enum GNUNET_MQ_Error error)
431 struct GNUNET_ARM_Handle *h = cls;
432 struct GNUNET_ARM_Operation *op;
434 h->currently_up = GNUNET_NO;
435 if (NULL != (op = h->thm))
438 op->result_cont (op->cont_cls,
439 GNUNET_ARM_REQUEST_SENT_OK,
440 GNUNET_ARM_RESULT_STOPPED);
443 reconnect_arm_later (h);
450 * @param h arm handle
451 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
454 reconnect_arm (struct GNUNET_ARM_Handle *h)
456 GNUNET_MQ_hd_fixed_size (arm_result,
457 GNUNET_MESSAGE_TYPE_ARM_RESULT,
458 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);
462 GNUNET_MQ_hd_fixed_size (confirm,
463 GNUNET_MESSAGE_TYPE_TEST,
464 struct GNUNET_MessageHeader);
465 struct GNUNET_MQ_MessageHandler handlers[] = {
466 make_arm_result_handler (h),
467 make_arm_list_result_handler (h),
468 make_confirm_handler (h),
469 GNUNET_MQ_handler_end ()
471 struct GNUNET_MessageHeader *test;
472 struct GNUNET_MQ_Envelope *env;
476 GNUNET_assert (GNUNET_NO == h->currently_up);
477 h->mq = GNUNET_CLIENT_connecT (h->cfg,
484 LOG (GNUNET_ERROR_TYPE_DEBUG,
485 "GNUNET_CLIENT_connect returned NULL\n");
486 if (NULL != h->conn_status)
487 h->conn_status (h->conn_status_cls,
489 return GNUNET_SYSERR;
491 LOG (GNUNET_ERROR_TYPE_DEBUG,
492 "Sending TEST message to ARM\n");
493 env = GNUNET_MQ_msg (test,
494 GNUNET_MESSAGE_TYPE_TEST);
495 GNUNET_MQ_send (h->mq,
502 * Set up a context for communicating with ARM, then
503 * start connecting to the ARM service using that context.
505 * @param cfg configuration to use (needed to contact ARM;
506 * the ARM service may internally use a different
507 * configuration to determine how to start the service).
508 * @param conn_status will be called when connecting/disconnecting
509 * @param conn_status_cls closure for @a conn_status
510 * @return context to use for further ARM operations, NULL on error.
512 struct GNUNET_ARM_Handle *
513 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
514 GNUNET_ARM_ConnectionStatusCallback conn_status,
515 void *conn_status_cls)
517 struct GNUNET_ARM_Handle *h;
519 h = GNUNET_new (struct GNUNET_ARM_Handle);
521 h->conn_status = conn_status;
522 h->conn_status_cls = conn_status_cls;
523 if (GNUNET_OK != reconnect_arm (h))
533 * Disconnect from the ARM service (if connected) and destroy the context.
535 * @param h the handle that was being used
538 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
540 struct GNUNET_ARM_Operation *op;
542 LOG (GNUNET_ERROR_TYPE_DEBUG,
543 "Disconnecting from ARM service\n");
544 while (NULL != (op = h->operation_pending_head))
546 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
547 h->operation_pending_tail,
549 if (NULL != op->result_cont)
550 op->result_cont (op->cont_cls,
551 GNUNET_ARM_REQUEST_DISCONNECTED,
553 if (NULL != op->list_cont)
554 op->list_cont (op->cont_cls,
555 GNUNET_ARM_REQUEST_DISCONNECTED,
558 if (NULL != op->async)
560 GNUNET_SCHEDULER_cancel (op->async);
567 GNUNET_MQ_destroy (h->mq);
570 if (NULL != h->reconnect_task)
572 GNUNET_SCHEDULER_cancel (h->reconnect_task);
573 h->reconnect_task = NULL;
580 * A client specifically requested starting of ARM itself.
581 * Starts the ARM service.
583 * @param h the handle with configuration details
584 * @param std_inheritance inheritance of std streams
585 * @return operation status code
587 static enum GNUNET_ARM_Result
588 start_arm_service (struct GNUNET_ARM_Handle *h,
589 enum GNUNET_OS_InheritStdioFlags std_inheritance)
591 struct GNUNET_OS_Process *proc;
600 GNUNET_CONFIGURATION_get_value_string (h->cfg,
604 loprefix = GNUNET_strdup ("");
606 GNUNET_CONFIGURATION_get_value_string (h->cfg,
610 lopostfix = GNUNET_strdup ("");
612 GNUNET_CONFIGURATION_get_value_string (h->cfg,
617 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
620 GNUNET_free (loprefix);
621 GNUNET_free (lopostfix);
622 return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
625 GNUNET_CONFIGURATION_get_value_filename (h->cfg,
630 binary = GNUNET_OS_get_libexec_binary_path (cbinary);
631 GNUNET_asprintf ("edbinary,
634 GNUNET_free (cbinary);
636 GNUNET_CONFIGURATION_have_value (h->cfg,
640 GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
644 GNUNET_CONFIGURATION_have_value (h->cfg,
648 /* Means we are ONLY running locally */
649 /* we're clearly running a test, don't daemonize */
651 proc = GNUNET_OS_start_process_s (GNUNET_NO,
656 /* no daemonization! */
660 proc = GNUNET_OS_start_process_s (GNUNET_NO,
666 /* no daemonization! */
673 proc = GNUNET_OS_start_process_s (GNUNET_NO,
678 "-d", /* do daemonize */
681 proc = GNUNET_OS_start_process_s (GNUNET_NO,
687 "-d", /* do daemonize */
691 GNUNET_free (binary);
692 GNUNET_free (quotedbinary);
693 GNUNET_free_non_null (config);
694 GNUNET_free (loprefix);
695 GNUNET_free (lopostfix);
697 return GNUNET_ARM_RESULT_START_FAILED;
698 GNUNET_OS_process_destroy (proc);
699 return GNUNET_ARM_RESULT_STARTING;
704 * Abort an operation. Only prevents the callback from being
705 * called, the operation may still complete.
707 * @param op operation to cancel
710 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
712 struct GNUNET_ARM_Handle *h = op->h;
716 op->result_cont = NULL;
719 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
720 h->operation_pending_tail,
727 * Start or stop a service.
729 * @param h handle to ARM
730 * @param service_name name of the service
731 * @param cb callback to invoke when service is ready
732 * @param cb_cls closure for @a cb
733 * @param type type of the request
734 * @return handle to queue, NULL on error
736 static struct GNUNET_ARM_Operation *
737 change_service (struct GNUNET_ARM_Handle *h,
738 const char *service_name,
739 GNUNET_ARM_ResultCallback cb,
743 struct GNUNET_ARM_Operation *op;
745 struct GNUNET_MQ_Envelope *env;
746 struct GNUNET_ARM_Message *msg;
748 slen = strlen (service_name) + 1;
749 if (slen + sizeof (struct GNUNET_ARM_Message) >=
750 GNUNET_SERVER_MAX_MESSAGE_SIZE)
755 if (0 == h->request_id_counter)
756 h->request_id_counter++;
757 op = GNUNET_new (struct GNUNET_ARM_Operation);
759 op->result_cont = cb;
760 op->cont_cls = cb_cls;
761 op->id = h->request_id_counter++;
762 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
763 h->operation_pending_tail,
765 env = GNUNET_MQ_msg_extra (msg,
768 msg->reserved = htonl (0);
769 msg->request_id = GNUNET_htonll (op->id);
773 GNUNET_MQ_send (h->mq,
780 * Task run to notify application that ARM is already up.
782 * @param cls the operation that asked ARM to be started
785 notify_running (void *cls)
787 struct GNUNET_ARM_Operation *op = cls;
788 struct GNUNET_ARM_Handle *h = op->h;
791 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
792 h->operation_pending_tail,
794 if (NULL != op->result_cont)
795 op->result_cont (op->cont_cls,
796 GNUNET_ARM_REQUEST_SENT_OK,
797 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
798 if ( (GNUNET_YES == h->currently_up) &&
799 (NULL != h->conn_status) )
800 h->conn_status (h->conn_status_cls,
807 * Task run to notify application that ARM is being started.
809 * @param cls the operation that asked ARM to be started
812 notify_starting (void *cls)
814 struct GNUNET_ARM_Operation *op = cls;
815 struct GNUNET_ARM_Handle *h = op->h;
818 LOG (GNUNET_ERROR_TYPE_DEBUG,
819 "Notifying client that we started the ARM service\n");
820 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
821 h->operation_pending_tail,
823 if (NULL != op->result_cont)
824 op->result_cont (op->cont_cls,
825 GNUNET_ARM_REQUEST_SENT_OK,
832 * Request for a service to be started.
834 * @param h handle to ARM
835 * @param service_name name of the service
836 * @param std_inheritance inheritance of std streams
837 * @param cont callback to invoke after request is sent or not sent
838 * @param cont_cls closure for @a cont
839 * @return handle for the operation, NULL on error
841 struct GNUNET_ARM_Operation *
842 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
843 const char *service_name,
844 enum GNUNET_OS_InheritStdioFlags std_inheritance,
845 GNUNET_ARM_ResultCallback cont,
848 struct GNUNET_ARM_Operation *op;
849 enum GNUNET_ARM_Result ret;
851 LOG (GNUNET_ERROR_TYPE_DEBUG,
852 "Starting service `%s'\n",
854 if (0 != strcasecmp ("arm",
856 return change_service (h,
860 GNUNET_MESSAGE_TYPE_ARM_START);
863 * 1) We're connected to ARM already. Invoke the callback immediately.
864 * 2) We're not connected to ARM.
865 * Cancel any reconnection attempts temporarily, then perform
868 if (GNUNET_YES == h->currently_up)
870 LOG (GNUNET_ERROR_TYPE_DEBUG,
871 "ARM is already running\n");
872 op = GNUNET_new (struct GNUNET_ARM_Operation);
874 op->result_cont = cont;
875 op->cont_cls = cont_cls;
876 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
877 h->operation_pending_tail,
879 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
883 /* This is an inherently uncertain choice, as it is of course
884 theoretically possible that ARM is up and we just did not
885 yet complete the MQ handshake. However, given that users
886 are unlikely to hammer 'gnunet-arm -s' on a busy system,
887 the above check should catch 99.99% of the cases where ARM
888 is already running. */
889 LOG (GNUNET_ERROR_TYPE_DEBUG,
890 "Starting ARM service\n");
891 ret = start_arm_service (h,
893 if (GNUNET_ARM_RESULT_STARTING == ret)
895 op = GNUNET_new (struct GNUNET_ARM_Operation);
897 op->result_cont = cont;
898 op->cont_cls = cont_cls;
899 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
900 h->operation_pending_tail,
902 op->starting_ret = ret;
903 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
910 * Request a service to be stopped. Stopping arm itself will not
911 * invalidate its handle, and ARM API will try to restore connection
912 * to the ARM service, even if ARM connection was lost because you
913 * asked for ARM to be stopped. Call
914 * #GNUNET_ARM_disconnect() to free the handle and prevent
915 * further connection attempts.
917 * @param h handle to ARM
918 * @param service_name name of the service
919 * @param cont callback to invoke after request is sent or is not sent
920 * @param cont_cls closure for @a cont
921 * @return handle for the operation, NULL on error
923 struct GNUNET_ARM_Operation *
924 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
925 const char *service_name,
926 GNUNET_ARM_ResultCallback cont,
929 struct GNUNET_ARM_Operation *op;
931 LOG (GNUNET_ERROR_TYPE_DEBUG,
932 "Stopping service `%s'\n",
934 op = change_service (h,
938 GNUNET_MESSAGE_TYPE_ARM_STOP);
941 /* If the service is ARM, set a flag as we will use MQ errors
942 to detect that the process is really gone. */
943 if (0 == strcasecmp (service_name,
945 op->is_arm_stop = GNUNET_YES;
951 * Request a list of running services.
953 * @param h handle to ARM
954 * @param cont callback to invoke after request is sent or is not sent
955 * @param cont_cls closure for @a cont
956 * @return handle for the operation, NULL on error
958 struct GNUNET_ARM_Operation *
959 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
960 GNUNET_ARM_ServiceListCallback cont,
963 struct GNUNET_ARM_Operation *op;
964 struct GNUNET_MQ_Envelope *env;
965 struct GNUNET_ARM_Message *msg;
967 LOG (GNUNET_ERROR_TYPE_DEBUG,
968 "Requesting LIST from ARM service\n");
969 if (0 == h->request_id_counter)
970 h->request_id_counter++;
971 op = GNUNET_new (struct GNUNET_ARM_Operation);
973 op->list_cont = cont;
974 op->cont_cls = cont_cls;
975 op->id = h->request_id_counter++;
976 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
977 h->operation_pending_tail,
979 env = GNUNET_MQ_msg (msg,
980 GNUNET_MESSAGE_TYPE_ARM_LIST);
981 msg->reserved = htonl (0);
982 msg->request_id = GNUNET_htonll (op->id);
983 GNUNET_MQ_send (h->mq,
989 /* end of arm_api.c */