ROS 2 rclcpp + rcl - jazzy  jazzy
ROS 2 C++ Client Library with ROS Client Library
init.c
1 // Copyright 2015 Open Source Robotics Foundation, Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifdef __cplusplus
16 extern "C"
17 {
18 #endif
19 
20 #include "rcl/init.h"
21 
22 #include "rcutils/logging_macros.h"
23 #include "rcutils/stdatomic_helper.h"
24 #include "rcutils/strdup.h"
25 
26 #include "rmw/error_handling.h"
27 
28 #include "tracetools/tracetools.h"
29 
30 #include "rcl/arguments.h"
31 #include "rcl/discovery_options.h"
32 #include "rcl/domain_id.h"
33 #include "rcl/error_handling.h"
34 #include "rcl/localhost.h"
35 #include "rcl/logging.h"
36 #include "rcl/security.h"
38 
39 #include "./arguments_impl.h"
40 #include "./common.h"
41 #include "./context_impl.h"
42 #include "./init_options_impl.h"
43 
44 static atomic_uint_least64_t __rcl_next_unique_id = ATOMIC_VAR_INIT(1);
45 
48  int argc,
49  char const * const * argv,
50  const rcl_init_options_t * options,
51  rcl_context_t * context)
52 {
53  rcl_ret_t fail_ret = RCL_RET_ERROR;
54 
55  if (argc > 0) {
56  RCL_CHECK_ARGUMENT_FOR_NULL(argv, RCL_RET_INVALID_ARGUMENT);
57  for (int i = 0; i < argc; ++i) {
58  RCL_CHECK_ARGUMENT_FOR_NULL(argv[i], RCL_RET_INVALID_ARGUMENT);
59  }
60  } else {
61  if (NULL != argv) {
62  RCL_SET_ERROR_MSG("argc is <= 0, but argv is not NULL");
64  }
65  }
66  RCL_CHECK_ARGUMENT_FOR_NULL(options, RCL_RET_INVALID_ARGUMENT);
67  RCL_CHECK_ARGUMENT_FOR_NULL(options->impl, RCL_RET_INVALID_ARGUMENT);
68  rcl_allocator_t allocator = options->impl->allocator;
70  RCL_CHECK_ARGUMENT_FOR_NULL(context, RCL_RET_INVALID_ARGUMENT);
71 
72  RCUTILS_LOG_DEBUG_NAMED(
73  ROS_PACKAGE_NAME,
74  "Initializing ROS client library, for context at address: %p", (void *) context);
75 
76  // test expectation that given context is zero initialized
77  if (NULL != context->impl) {
78  // note that this can also occur when the given context is used before initialization
79  // i.e. it is declared on the stack but never defined or zero initialized
80  RCL_SET_ERROR_MSG("rcl_init called on an already initialized context");
81  return RCL_RET_ALREADY_INIT;
82  }
83 
84  // Zero initialize global arguments.
86 
87  // Setup impl for context.
88  // use zero_allocate so the cleanup function will not try to clean up uninitialized parts later
89  context->impl = allocator.zero_allocate(1, sizeof(rcl_context_impl_t), allocator.state);
90  RCL_CHECK_FOR_NULL_WITH_MSG(
91  context->impl, "failed to allocate memory for context impl", return RCL_RET_BAD_ALLOC);
92 
93  // Zero initialize rmw context first so its validity can by checked in cleanup.
94  context->impl->rmw_context = rmw_get_zero_initialized_context();
95 
96  // Store the allocator.
97  context->impl->allocator = allocator;
98 
99  // Copy the options into the context for future reference.
100  rcl_ret_t ret = rcl_init_options_copy(options, &(context->impl->init_options));
101  if (RCL_RET_OK != ret) {
102  fail_ret = ret; // error message already set
103  goto fail;
104  }
105 
106  // Copy the argc and argv into the context, if argc >= 0.
107  context->impl->argc = argc;
108  context->impl->argv = NULL;
109  if (0 != argc && argv != NULL) {
110  context->impl->argv = (char **)allocator.zero_allocate(argc, sizeof(char *), allocator.state);
111  RCL_CHECK_FOR_NULL_WITH_MSG(
112  context->impl->argv,
113  "failed to allocate memory for argv",
114  fail_ret = RCL_RET_BAD_ALLOC; goto fail);
115  int64_t i;
116  for (i = 0; i < argc; ++i) {
117  size_t argv_i_length = strlen(argv[i]) + 1;
118  context->impl->argv[i] = (char *)allocator.allocate(argv_i_length, allocator.state);
119  RCL_CHECK_FOR_NULL_WITH_MSG(
120  context->impl->argv[i],
121  "failed to allocate memory for string entry in argv",
122  fail_ret = RCL_RET_BAD_ALLOC; goto fail);
123  memcpy(context->impl->argv[i], argv[i], argv_i_length);
124  }
125  }
126 
127  // Parse the ROS specific arguments.
128  ret = rcl_parse_arguments(argc, argv, allocator, &context->global_arguments);
129  if (RCL_RET_OK != ret) {
130  fail_ret = ret;
131  RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to parse global arguments");
132  goto fail;
133  }
134 
135  // Set the instance id.
136  uint64_t next_instance_id = rcutils_atomic_fetch_add_uint64_t(&__rcl_next_unique_id, 1);
137  if (0 == next_instance_id) {
138  // Roll over occurred, this is an extremely unlikely occurrence.
139  RCL_SET_ERROR_MSG("unique rcl instance ids exhausted");
140  // Roll back to try to avoid the next call succeeding, but there's a data race here.
141  rcutils_atomic_store(&__rcl_next_unique_id, -1);
142  goto fail;
143  }
144  rcutils_atomic_store((atomic_uint_least64_t *)(&context->instance_id_storage), next_instance_id);
145  context->impl->init_options.impl->rmw_init_options.instance_id = next_instance_id;
146 
147  size_t * domain_id = &context->impl->init_options.impl->rmw_init_options.domain_id;
148  if (RCL_DEFAULT_DOMAIN_ID == *domain_id) {
149  // Get actual domain id based on environment variable.
150  ret = rcl_get_default_domain_id(domain_id);
151  if (RCL_RET_OK != ret) {
152  fail_ret = ret;
153  goto fail;
154  }
155  }
156 
157  rmw_localhost_only_t * localhost_only =
158  &context->impl->init_options.impl->rmw_init_options.localhost_only;
159  if (RMW_LOCALHOST_ONLY_DEFAULT != *localhost_only) {
160  RCUTILS_LOG_WARN_NAMED(
161  ROS_PACKAGE_NAME,
162  "'localhost_only' init option is deprecated but still honored if it is enabled. "
163  "Use 'automatic_discovery_range' and 'static_peers' instead.");
164  } else {
165  // Get actual localhost_only value based on environment variable, if needed.
166  ret = rcl_get_localhost_only(localhost_only);
167  if (RCL_RET_OK != ret) {
168  fail_ret = ret;
169  goto fail;
170  }
171  if (RMW_LOCALHOST_ONLY_DEFAULT != *localhost_only) {
172  RCUTILS_LOG_WARN_NAMED(
173  ROS_PACKAGE_NAME,
174  "ROS_LOCALHOST_ONLY is deprecated but still honored if it is enabled. "
175  "Use ROS_AUTOMATIC_DISCOVERY_RANGE and ROS_STATIC_PEERS instead.");
176  }
177  }
178 
179  const rmw_discovery_options_t original_discovery_options =
180  options->impl->rmw_init_options.discovery_options;
181  rmw_discovery_options_t * discovery_options =
182  &context->impl->init_options.impl->rmw_init_options.discovery_options;
183 
184  // this happens either rmw implementation forces or via environmental variable.
185  // localhost_only is deprecated but still honored to prevail discovery_options.
186  // see https://github.com/ros2/ros2_documentation/pull/3519#discussion_r1186541935
187  // TODO(fujitatomoya): remove localhost_only completely after deprecation period.
188  if (*localhost_only == RMW_LOCALHOST_ONLY_ENABLED) {
189  RCUTILS_LOG_WARN_NAMED(
190  ROS_PACKAGE_NAME,
191  "'localhost_only' is enabled, "
192  "'automatic_discovery_range' and 'static_peers' will be ignored.");
193  discovery_options->automatic_discovery_range = RMW_AUTOMATIC_DISCOVERY_RANGE_LOCALHOST;
194  discovery_options->static_peers_count = 0;
195  } else {
196  if (*localhost_only == RMW_LOCALHOST_ONLY_DISABLED) {
197  RCUTILS_LOG_WARN_NAMED(
198  ROS_PACKAGE_NAME,
199  "'localhost_only' is disabled, "
200  "'automatic_discovery_range' and 'static_peers' will be used.");
201  }
202 
203  // Get actual discovery range option based on environment variable, if not given
204  // to original options passed to function
205  if ( // NOLINT
206  RMW_AUTOMATIC_DISCOVERY_RANGE_NOT_SET == original_discovery_options.automatic_discovery_range)
207  {
208  ret = rcl_get_automatic_discovery_range(discovery_options);
209  if (RCL_RET_OK != ret) {
210  fail_ret = ret;
211  goto fail;
212  }
213  }
214 
215  if (0 == discovery_options->static_peers_count &&
216  discovery_options->automatic_discovery_range != RMW_AUTOMATIC_DISCOVERY_RANGE_OFF)
217  {
218  // Get static peers.
219  // If off is set, it makes sense to not get any static peers.
220  ret = rcl_get_discovery_static_peers(discovery_options, &allocator);
221  if (RCL_RET_OK != ret) {
222  fail_ret = ret;
223  goto fail;
224  }
225  }
226 
227  if (discovery_options->static_peers_count > 0 &&
228  discovery_options->automatic_discovery_range == RMW_AUTOMATIC_DISCOVERY_RANGE_OFF)
229  {
230  RCUTILS_LOG_WARN_NAMED(
231  ROS_PACKAGE_NAME,
232  "Note: ROS_AUTOMATIC_DISCOVERY_RANGE is set to OFF, but "
233  "found static peers in ROS_STATIC_PEERS. "
234  "ROS_STATIC_PEERS will be ignored.");
235  }
236  }
237 
238  const char * discovery_range_string =
239  rcl_automatic_discovery_range_to_string(discovery_options->automatic_discovery_range);
240  if (NULL == discovery_range_string) {
241  discovery_range_string = "not recognized";
242  }
243  RCUTILS_LOG_DEBUG_NAMED(
244  ROS_PACKAGE_NAME,
245  "Automatic discovery range is %s (%d)",
246  discovery_range_string,
247  discovery_options->automatic_discovery_range);
248  RCUTILS_LOG_DEBUG_NAMED(
249  ROS_PACKAGE_NAME,
250  "Static peers count is %lu",
251  discovery_options->static_peers_count);
252 
253  for (size_t ii = 0; ii < discovery_options->static_peers_count; ++ii) {
254  RCUTILS_LOG_DEBUG_NAMED(
255  ROS_PACKAGE_NAME,
256  "\t%s", discovery_options->static_peers[ii].peer_address);
257  }
258 
259  if (context->global_arguments.impl->enclave) {
260  context->impl->init_options.impl->rmw_init_options.enclave = rcutils_strdup(
261  context->global_arguments.impl->enclave,
262  context->impl->allocator);
263  } else {
264  context->impl->init_options.impl->rmw_init_options.enclave = rcutils_strdup(
265  "/", context->impl->allocator);
266  }
267 
268  if (!context->impl->init_options.impl->rmw_init_options.enclave) {
269  RCL_SET_ERROR_MSG("failed to set context name");
270  fail_ret = RCL_RET_BAD_ALLOC;
271  goto fail;
272  }
273 
274  int validation_result;
275  size_t invalid_index;
277  context->impl->init_options.impl->rmw_init_options.enclave,
278  &validation_result,
279  &invalid_index);
280  if (RCL_RET_OK != ret) {
281  RCL_SET_ERROR_MSG("rcl_validate_enclave_name() failed");
282  fail_ret = ret;
283  goto fail;
284  }
285  if (RCL_ENCLAVE_NAME_VALID != validation_result) {
286  RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
287  "Enclave name is not valid: '%s'. Invalid index: %zu",
289  invalid_index);
290  fail_ret = RCL_RET_ERROR;
291  goto fail;
292  }
293 
294  rmw_security_options_t * security_options =
295  &context->impl->init_options.impl->rmw_init_options.security_options;
297  context->impl->init_options.impl->rmw_init_options.enclave,
298  &context->impl->allocator,
299  security_options);
300  if (RCL_RET_OK != ret) {
301  fail_ret = ret;
302  goto fail;
303  }
304 
305  // Initialize rmw_init.
306  rmw_ret_t rmw_ret = rmw_init(
307  &(context->impl->init_options.impl->rmw_init_options),
308  &(context->impl->rmw_context));
309  if (RMW_RET_OK != rmw_ret) {
310  RCL_SET_ERROR_MSG(rmw_get_error_string().str);
311  fail_ret = rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
312  goto fail;
313  }
314 
315  TRACETOOLS_TRACEPOINT(rcl_init, (const void *)context);
316 
317  return RCL_RET_OK;
318 fail:
319  __cleanup_context(context);
320  return fail_ret;
321 }
322 
323 rcl_ret_t
325 {
326  RCUTILS_LOG_DEBUG_NAMED(
327  ROS_PACKAGE_NAME,
328  "Shutting down ROS client library, for context at address: %p", (void *) context);
329  RCL_CHECK_ARGUMENT_FOR_NULL(context, RCL_RET_INVALID_ARGUMENT);
330  RCL_CHECK_FOR_NULL_WITH_MSG(
331  context->impl, "context is zero-initialized", return RCL_RET_INVALID_ARGUMENT);
332  if (!rcl_context_is_valid(context)) {
333  RCL_SET_ERROR_MSG("rcl_shutdown already called on the given context");
335  }
336 
337  rmw_ret_t rmw_ret = rmw_shutdown(&(context->impl->rmw_context));
338  if (RMW_RET_OK != rmw_ret) {
339  RCL_SET_ERROR_MSG(rmw_get_error_string().str);
340  return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
341  }
342 
343  // reset the instance id to 0 to indicate "invalid"
344  rcutils_atomic_store((atomic_uint_least64_t *)(&context->instance_id_storage), 0);
345 
346  return RCL_RET_OK;
347 }
348 
349 #ifdef __cplusplus
350 }
351 #endif
#define RCL_CHECK_ALLOCATOR(allocator, fail_statement)
Check that the given allocator is initialized.
Definition: allocator.h:49
rcutils_allocator_t rcl_allocator_t
Encapsulation of an allocator.
Definition: allocator.h:31
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_parse_arguments(int argc, const char *const *argv, rcl_allocator_t allocator, rcl_arguments_t *args_output)
Parse command line arguments into a structure usable by code.
RCL_PUBLIC RCL_WARN_UNUSED rcl_arguments_t rcl_get_zero_initialized_arguments(void)
Return a rcl_arguments_t struct with members initialized to NULL.
RCL_PUBLIC RCL_WARN_UNUSED bool rcl_context_is_valid(const rcl_context_t *context)
Return true if the given context is currently valid, otherwise false.
Definition: context.c:94
RCL_PUBLIC rcl_ret_t rcl_get_discovery_static_peers(rmw_discovery_options_t *discovery_options, rcutils_allocator_t *allocator)
Determine how the user wishes to discover other ROS nodes via statically-configured peers.
RCL_PUBLIC const char * rcl_automatic_discovery_range_to_string(rmw_automatic_discovery_range_t automatic_discovery_range)
Convert the automatic discovery range value to a string for easy printing.
RCL_PUBLIC rcl_ret_t rcl_get_automatic_discovery_range(rmw_discovery_options_t *discovery_options)
Determine how the user wishes to discover other ROS nodes automatically.
RCL_PUBLIC rcl_ret_t rcl_get_default_domain_id(size_t *domain_id)
Determine the default domain ID, based on the environment.
Definition: domain_id.c:28
#define RCL_DEFAULT_DOMAIN_ID
The default domain ID used by RCL.
Definition: domain_id.h:32
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_shutdown(rcl_context_t *context)
Shutdown a given rcl context.
Definition: init.c:324
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_init(int argc, char const *const *argv, const rcl_init_options_t *options, rcl_context_t *context)
Initialization of rcl.
Definition: init.c:47
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_init_options_copy(const rcl_init_options_t *src, rcl_init_options_t *dst)
Copy the given source init_options to the destination init_options.
Definition: init_options.c:88
RCL_PUBLIC rcl_ret_t rcl_get_localhost_only(rmw_localhost_only_t *localhost_only)
Determine if the user wants to communicate using loopback only.
Definition: localhost.c:28
RCL_PUBLIC rcl_ret_t rcl_get_security_options_from_environment(const char *name, const rcutils_allocator_t *allocator, rmw_security_options_t *security_options)
Initialize security options from values in the environment variables and given names.
Definition: security.c:29
char * enclave
Enclave to be used.
rcl_arguments_impl_t * impl
Private implementation pointer.
Definition: arguments.h:38
char ** argv
Copy of argv used during init (may be NULL).
Definition: context_impl.h:38
rmw_context_t rmw_context
rmw context.
Definition: context_impl.h:40
int64_t argc
Length of argv (may be 0).
Definition: context_impl.h:36
rcl_init_options_t init_options
Copy of init options given during init.
Definition: context_impl.h:34
rcl_allocator_t allocator
Allocator used during init and shutdown.
Definition: context_impl.h:32
Encapsulates the non-global state of an init/shutdown cycle.
Definition: context.h:114
rcl_context_impl_t * impl
Implementation specific pointer.
Definition: context.h:120
rcl_arguments_t global_arguments
Global arguments for all nodes which share this context.
Definition: context.h:117
Encapsulation of init options and implementation defined init options.
Definition: init_options.h:36
rcl_init_options_impl_t * impl
Implementation specific pointer.
Definition: init_options.h:38
#define RCL_RET_ALREADY_INIT
rcl_init() already called return code.
Definition: types.h:41
#define RCL_RET_OK
Success return code.
Definition: types.h:27
#define RCL_RET_BAD_ALLOC
Failed to allocate memory return code.
Definition: types.h:33
#define RCL_RET_INVALID_ARGUMENT
Invalid argument return code.
Definition: types.h:35
#define RCL_RET_ERROR
Unspecified error return code.
Definition: types.h:29
#define RCL_RET_ALREADY_SHUTDOWN
rcl_shutdown() already called return code.
Definition: types.h:53
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:24
#define RCL_ENCLAVE_NAME_VALID
The enclave name is valid.
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_validate_enclave_name(const char *enclave, int *validation_result, size_t *invalid_index)
Determine if a given enclave name is valid.
RCL_PUBLIC RCL_WARN_UNUSED const char * rcl_enclave_name_validation_result_string(int validation_result)
Return a validation result description, or NULL if unknown or RCL_ENCLAVE_NAME_VALID.