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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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) - sizeof (*lres);
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) - sizeof (*lres);
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 struct GNUNET_MQ_MessageHandler handlers[] = {
457 GNUNET_MQ_hd_fixed_size (arm_result,
458 GNUNET_MESSAGE_TYPE_ARM_RESULT,
459 struct GNUNET_ARM_ResultMessage,
461 GNUNET_MQ_hd_var_size (arm_list_result,
462 GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT,
463 struct GNUNET_ARM_ListResultMessage,
465 GNUNET_MQ_hd_fixed_size (confirm,
466 GNUNET_MESSAGE_TYPE_ARM_TEST,
467 struct GNUNET_MessageHeader,
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_ARM_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 loprefix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
609 GNUNET_CONFIGURATION_get_value_string (h->cfg,
613 lopostfix = GNUNET_strdup ("");
615 lopostfix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
618 GNUNET_CONFIGURATION_get_value_string (h->cfg,
623 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
626 GNUNET_free (loprefix);
627 GNUNET_free (lopostfix);
628 return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
631 GNUNET_CONFIGURATION_get_value_filename (h->cfg,
636 binary = GNUNET_OS_get_libexec_binary_path (cbinary);
637 GNUNET_asprintf ("edbinary,
640 GNUNET_free (cbinary);
642 GNUNET_CONFIGURATION_have_value (h->cfg,
646 GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
650 GNUNET_CONFIGURATION_have_value (h->cfg,
654 /* Means we are ONLY running locally */
655 /* we're clearly running a test, don't daemonize */
657 proc = GNUNET_OS_start_process_s (GNUNET_NO,
662 /* no daemonization! */
666 proc = GNUNET_OS_start_process_s (GNUNET_NO,
672 /* no daemonization! */
679 proc = GNUNET_OS_start_process_s (GNUNET_NO,
684 "-d", /* do daemonize */
688 proc = GNUNET_OS_start_process_s (GNUNET_NO,
694 "-d", /* do daemonize */
698 GNUNET_free (binary);
699 GNUNET_free (quotedbinary);
700 GNUNET_free_non_null (config);
701 GNUNET_free (loprefix);
702 GNUNET_free (lopostfix);
704 return GNUNET_ARM_RESULT_START_FAILED;
705 GNUNET_OS_process_destroy (proc);
706 return GNUNET_ARM_RESULT_STARTING;
711 * Abort an operation. Only prevents the callback from being
712 * called, the operation may still complete.
714 * @param op operation to cancel
717 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
719 struct GNUNET_ARM_Handle *h = op->h;
723 op->result_cont = NULL;
726 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
727 h->operation_pending_tail,
734 * Start or stop a service.
736 * @param h handle to ARM
737 * @param service_name name of the service
738 * @param cb callback to invoke when service is ready
739 * @param cb_cls closure for @a cb
740 * @param type type of the request
741 * @return handle to queue, NULL on error
743 static struct GNUNET_ARM_Operation *
744 change_service (struct GNUNET_ARM_Handle *h,
745 const char *service_name,
746 GNUNET_ARM_ResultCallback cb,
750 struct GNUNET_ARM_Operation *op;
752 struct GNUNET_MQ_Envelope *env;
753 struct GNUNET_ARM_Message *msg;
755 slen = strlen (service_name) + 1;
756 if (slen + sizeof (struct GNUNET_ARM_Message) >=
757 GNUNET_MAX_MESSAGE_SIZE)
762 if (0 == h->request_id_counter)
763 h->request_id_counter++;
764 op = GNUNET_new (struct GNUNET_ARM_Operation);
766 op->result_cont = cb;
767 op->cont_cls = cb_cls;
768 op->id = h->request_id_counter++;
769 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
770 h->operation_pending_tail,
772 env = GNUNET_MQ_msg_extra (msg,
775 msg->reserved = htonl (0);
776 msg->request_id = GNUNET_htonll (op->id);
777 GNUNET_memcpy (&msg[1],
780 GNUNET_MQ_send (h->mq,
787 * Task run to notify application that ARM is already up.
789 * @param cls the operation that asked ARM to be started
792 notify_running (void *cls)
794 struct GNUNET_ARM_Operation *op = cls;
795 struct GNUNET_ARM_Handle *h = op->h;
798 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
799 h->operation_pending_tail,
801 if (NULL != op->result_cont)
802 op->result_cont (op->cont_cls,
803 GNUNET_ARM_REQUEST_SENT_OK,
804 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
805 if ( (GNUNET_YES == h->currently_up) &&
806 (NULL != h->conn_status) )
807 h->conn_status (h->conn_status_cls,
814 * Task run to notify application that ARM is being started.
816 * @param cls the operation that asked ARM to be started
819 notify_starting (void *cls)
821 struct GNUNET_ARM_Operation *op = cls;
822 struct GNUNET_ARM_Handle *h = op->h;
825 LOG (GNUNET_ERROR_TYPE_DEBUG,
826 "Notifying client that we started the ARM service\n");
827 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
828 h->operation_pending_tail,
830 if (NULL != op->result_cont)
831 op->result_cont (op->cont_cls,
832 GNUNET_ARM_REQUEST_SENT_OK,
839 * Request for a service to be started.
841 * @param h handle to ARM
842 * @param service_name name of the service
843 * @param std_inheritance inheritance of std streams
844 * @param cont callback to invoke after request is sent or not sent
845 * @param cont_cls closure for @a cont
846 * @return handle for the operation, NULL on error
848 struct GNUNET_ARM_Operation *
849 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
850 const char *service_name,
851 enum GNUNET_OS_InheritStdioFlags std_inheritance,
852 GNUNET_ARM_ResultCallback cont,
855 struct GNUNET_ARM_Operation *op;
856 enum GNUNET_ARM_Result ret;
858 LOG (GNUNET_ERROR_TYPE_DEBUG,
859 "Starting service `%s'\n",
861 if (0 != strcasecmp ("arm",
863 return change_service (h,
867 GNUNET_MESSAGE_TYPE_ARM_START);
870 * 1) We're connected to ARM already. Invoke the callback immediately.
871 * 2) We're not connected to ARM.
872 * Cancel any reconnection attempts temporarily, then perform
875 if (GNUNET_YES == h->currently_up)
877 LOG (GNUNET_ERROR_TYPE_DEBUG,
878 "ARM is already running\n");
879 op = GNUNET_new (struct GNUNET_ARM_Operation);
881 op->result_cont = cont;
882 op->cont_cls = cont_cls;
883 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
884 h->operation_pending_tail,
886 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
890 /* This is an inherently uncertain choice, as it is of course
891 theoretically possible that ARM is up and we just did not
892 yet complete the MQ handshake. However, given that users
893 are unlikely to hammer 'gnunet-arm -s' on a busy system,
894 the above check should catch 99.99% of the cases where ARM
895 is already running. */
896 LOG (GNUNET_ERROR_TYPE_DEBUG,
897 "Starting ARM service\n");
898 ret = start_arm_service (h,
900 if (GNUNET_ARM_RESULT_STARTING == ret)
902 op = GNUNET_new (struct GNUNET_ARM_Operation);
904 op->result_cont = cont;
905 op->cont_cls = cont_cls;
906 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
907 h->operation_pending_tail,
909 op->starting_ret = ret;
910 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
917 * Request a service to be stopped. Stopping arm itself will not
918 * invalidate its handle, and ARM API will try to restore connection
919 * to the ARM service, even if ARM connection was lost because you
920 * asked for ARM to be stopped. Call
921 * #GNUNET_ARM_disconnect() to free the handle and prevent
922 * further connection attempts.
924 * @param h handle to ARM
925 * @param service_name name of the service
926 * @param cont callback to invoke after request is sent or is not sent
927 * @param cont_cls closure for @a cont
928 * @return handle for the operation, NULL on error
930 struct GNUNET_ARM_Operation *
931 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
932 const char *service_name,
933 GNUNET_ARM_ResultCallback cont,
936 struct GNUNET_ARM_Operation *op;
938 LOG (GNUNET_ERROR_TYPE_DEBUG,
939 "Stopping service `%s'\n",
941 op = change_service (h,
945 GNUNET_MESSAGE_TYPE_ARM_STOP);
948 /* If the service is ARM, set a flag as we will use MQ errors
949 to detect that the process is really gone. */
950 if (0 == strcasecmp (service_name,
952 op->is_arm_stop = GNUNET_YES;
958 * Request a list of running services.
960 * @param h handle to ARM
961 * @param cont callback to invoke after request is sent or is not sent
962 * @param cont_cls closure for @a cont
963 * @return handle for the operation, NULL on error
965 struct GNUNET_ARM_Operation *
966 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
967 GNUNET_ARM_ServiceListCallback cont,
970 struct GNUNET_ARM_Operation *op;
971 struct GNUNET_MQ_Envelope *env;
972 struct GNUNET_ARM_Message *msg;
974 LOG (GNUNET_ERROR_TYPE_DEBUG,
975 "Requesting LIST from ARM service\n");
976 if (0 == h->request_id_counter)
977 h->request_id_counter++;
978 op = GNUNET_new (struct GNUNET_ARM_Operation);
980 op->list_cont = cont;
981 op->cont_cls = cont_cls;
982 op->id = h->request_id_counter++;
983 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
984 h->operation_pending_tail,
986 env = GNUNET_MQ_msg (msg,
987 GNUNET_MESSAGE_TYPE_ARM_LIST);
988 msg->reserved = htonl (0);
989 msg->request_id = GNUNET_htonll (op->id);
990 GNUNET_MQ_send (h->mq,
996 /* end of arm_api.c */