diff --git a/rcl/include/rcl/arguments.h b/rcl/include/rcl/arguments.h index 96c239b7b..55612e955 100644 --- a/rcl/include/rcl/arguments.h +++ b/rcl/include/rcl/arguments.h @@ -174,6 +174,34 @@ rcl_remove_ros_arguments( int * nonros_argc, const char ** nonros_argv[]); +/// Copy one arguments structure into another. +/** + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] error_alloc an alocator to use if an error occurs. + * This allocator is not used to allocate args_out. + * \param[in] args The structure to be copied. + * Its allocator is used to copy memory into the new structure. + * \param[out] args_out A zero-initialized arguments structure to be copied into. + * \return `RCL_RET_OK` if the structure was copied successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or + * \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_arguments_copy( + rcl_allocator_t error_alloc, + const rcl_arguments_t * args, + rcl_arguments_t * args_out); + /// Reclaim resources held inside rcl_arguments_t structure. /** *
diff --git a/rcl/include/rcl/node.h b/rcl/include/rcl/node.h index 7fa25b65b..ddb9f168d 100644 --- a/rcl/include/rcl/node.h +++ b/rcl/include/rcl/node.h @@ -152,7 +152,9 @@ rcl_get_zero_initialized_node(void); * \param[inout] node a preallocated rcl_node_t * \param[in] name the name of the node, must be a valid c-string * \param[in] namespace_ the namespace of the node, must be a valid c-string - * \param[in] options the node options + * \param[in] options the node options. + * The options are deep copied into the node. + * The caller is always responsible for freeing memory used options they pass in. * \return `RCL_RET_OK` if the node was initialized successfully, or * \return `RCL_RET_ALREADY_INIT` if the node has already be initialized, or * \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or @@ -208,6 +210,34 @@ RCL_PUBLIC rcl_node_options_t rcl_node_get_default_options(void); +/// Copy one options structure into another. +/** + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] error_alloc an alocator to use if an error occurs. + * This allocator is not used to allocate the output. + * \param[in] options The structure to be copied. + * Its allocator is used to copy memory into the new structure. + * \param[out] options_out An options structure containing default values. + * \return `RCL_RET_OK` if the structure was copied successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or + * \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_node_options_copy( + rcl_allocator_t error_alloc, + const rcl_node_options_t * options, + rcl_node_options_t * options_out); + /// Return `true` if the node is valid, else `false`. /** * Also return `false` if the node pointer is `NULL` or the allocator is invalid. diff --git a/rcl/src/rcl/arguments.c b/rcl/src/rcl/arguments.c index 636f156cf..81874c3dd 100644 --- a/rcl/src/rcl/arguments.c +++ b/rcl/src/rcl/arguments.c @@ -242,6 +242,72 @@ rcl_remove_ros_arguments( return RCL_RET_OK; } +rcl_ret_t +rcl_arguments_copy( + rcl_allocator_t error_alloc, + const rcl_arguments_t * args, + rcl_arguments_t * args_out) +{ + RCL_CHECK_ALLOCATOR_WITH_MSG(&error_alloc, "invalid allocator", return RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(args, RCL_RET_INVALID_ARGUMENT, error_alloc); + RCL_CHECK_ARGUMENT_FOR_NULL(args->impl, RCL_RET_INVALID_ARGUMENT, error_alloc); + RCL_CHECK_ARGUMENT_FOR_NULL(args_out, RCL_RET_INVALID_ARGUMENT, error_alloc); + if (NULL != args_out->impl) { + RCL_SET_ERROR_MSG("args_out must be zero initialized", error_alloc); + return RCL_RET_INVALID_ARGUMENT; + } + + rcl_allocator_t allocator = args->impl->allocator; + + args_out->impl = allocator.allocate(sizeof(rcl_arguments_impl_t), allocator.state); + if (NULL == args_out->impl) { + return RCL_RET_BAD_ALLOC; + } + + args_out->impl->allocator = allocator; + + // Zero so it's safe to call rcl_arguments_fini() if an error occurrs while copying. + args_out->impl->num_remap_rules = 0; + args_out->impl->num_unparsed_args = 0; + + // Copy unparsed args + args_out->impl->unparsed_args = allocator.allocate( + sizeof(int) * args->impl->num_unparsed_args, allocator.state); + if (NULL == args_out->impl->unparsed_args) { + if (RCL_RET_OK != rcl_arguments_fini(args_out)) { + RCL_SET_ERROR_MSG("Error while finalizing arguments due to another error", error_alloc); + } + return RCL_RET_BAD_ALLOC; + } + for (int i = 0; i < args->impl->num_unparsed_args; ++i) { + args_out->impl->unparsed_args[i] = args->impl->unparsed_args[i]; + } + args_out->impl->num_unparsed_args = args->impl->num_unparsed_args; + + // Copy remap rules + args_out->impl->remap_rules = allocator.allocate( + sizeof(rcl_remap_t) * args->impl->num_remap_rules, allocator.state); + if (NULL == args_out->impl->remap_rules) { + if (RCL_RET_OK != rcl_arguments_fini(args_out)) { + RCL_SET_ERROR_MSG("Error while finalizing arguments due to another error", error_alloc); + } + return RCL_RET_BAD_ALLOC; + } + args_out->impl->num_remap_rules = args->impl->num_remap_rules; + for (int i = 0; i < args->impl->num_remap_rules; ++i) { + args_out->impl->remap_rules[i] = rcl_remap_get_zero_initialized(); + rcl_ret_t ret = rcl_remap_copy( + error_alloc, &(args->impl->remap_rules[i]), &(args_out->impl->remap_rules[i])); + if (RCL_RET_OK != ret) { + if (RCL_RET_OK != rcl_arguments_fini(args_out)) { + RCL_SET_ERROR_MSG("Error while finalizing arguments due to another error", error_alloc); + } + return ret; + } + } + return RCL_RET_OK; +} + rcl_ret_t rcl_arguments_fini( rcl_arguments_t * args) diff --git a/rcl/src/rcl/node.c b/rcl/src/rcl/node.c index 0f0e294dc..33ba7a3c6 100644 --- a/rcl/src/rcl/node.c +++ b/rcl/src/rcl/node.c @@ -222,9 +222,12 @@ rcl_node_init( node->impl->rmw_node_handle = NULL; node->impl->graph_guard_condition = NULL; node->impl->logger_name = NULL; + node->impl->options = rcl_node_get_default_options(); // Initialize node impl. - // node options (assume it is trivially copyable) - node->impl->options = *options; + ret = rcl_node_options_copy(*allocator, options, &(node->impl->options)); + if (RCL_RET_OK != ret) { + goto fail; + } // Remap the node name and namespace if remap rules are given rcl_arguments_t * global_args = NULL; @@ -383,6 +386,15 @@ rcl_node_init( } allocator->deallocate(node->impl->graph_guard_condition, allocator->state); } + if (NULL != node->impl->options.arguments.impl) { + ret = rcl_arguments_fini(&(node->impl->options.arguments)); + if (ret != RCL_RET_OK) { + RCUTILS_LOG_ERROR_NAMED( + ROS_PACKAGE_NAME, + "failed to fini arguments in error recovery: %s", rcl_get_error_string_safe() + ) + } + } allocator->deallocate(node->impl, allocator->state); } *node = rcl_get_zero_initialized_node(); @@ -424,6 +436,12 @@ rcl_node_fini(rcl_node_t * node) allocator.deallocate(node->impl->graph_guard_condition, allocator.state); // assuming that allocate and deallocate are ok since they are checked in init allocator.deallocate((char *)node->impl->logger_name, allocator.state); + if (NULL != node->impl->options.arguments.impl) { + rcl_ret_t ret = rcl_arguments_fini(&(node->impl->options.arguments)); + if (ret != RCL_RET_OK) { + return ret; + } + } allocator.deallocate(node->impl, allocator.state); node->impl = NULL; RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Node finalized") @@ -462,6 +480,31 @@ rcl_node_get_default_options() return default_options; } +rcl_ret_t +rcl_node_options_copy( + rcl_allocator_t error_alloc, + const rcl_node_options_t * options, + rcl_node_options_t * options_out) +{ + RCL_CHECK_ALLOCATOR_WITH_MSG(&error_alloc, "invalid allocator", return RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(options, RCL_RET_INVALID_ARGUMENT, error_alloc); + RCL_CHECK_ARGUMENT_FOR_NULL(options_out, RCL_RET_INVALID_ARGUMENT, error_alloc); + if (options_out == options) { + RCL_SET_ERROR_MSG( + "Attempted to copy options into itself", error_alloc); + return RCL_RET_INVALID_ARGUMENT; + } + options_out->domain_id = options->domain_id; + options_out->allocator = options->allocator; + options_out->use_global_arguments = options->use_global_arguments; + if (NULL != options->arguments.impl) { + rcl_ret_t ret = rcl_arguments_copy( + error_alloc, &(options->arguments), &(options_out->arguments)); + return ret; + } + return RCL_RET_OK; +} + const char * rcl_node_get_name(const rcl_node_t * node) { diff --git a/rcl/src/rcl/remap.c b/rcl/src/rcl/remap.c index 690d161d6..aa155c784 100644 --- a/rcl/src/rcl/remap.c +++ b/rcl/src/rcl/remap.c @@ -39,6 +39,45 @@ rcl_remap_get_zero_initialized() return rule; } +rcl_ret_t +rcl_remap_copy( + rcl_allocator_t error_alloc, + const rcl_remap_t * rule, + rcl_remap_t * rule_out) +{ + RCL_CHECK_ALLOCATOR_WITH_MSG(&error_alloc, "invalid allocator", return RCL_RET_INVALID_ARGUMENT); + RCL_CHECK_ARGUMENT_FOR_NULL(rule, RCL_RET_INVALID_ARGUMENT, error_alloc); + RCL_CHECK_ARGUMENT_FOR_NULL(rule_out, RCL_RET_INVALID_ARGUMENT, error_alloc); + + rcl_allocator_t allocator = rule->allocator; + rule_out->allocator = allocator; + rule_out->type = rule->type; + if (NULL != rule->node_name) { + rule_out->node_name = rcutils_strdup(rule->node_name, allocator); + if (NULL == rule_out->node_name) { + goto fail; + } + } + if (NULL != rule->match) { + rule_out->match = rcutils_strdup(rule->match, allocator); + if (NULL == rule_out->match) { + goto fail; + } + } + if (NULL != rule->replacement) { + rule_out->replacement = rcutils_strdup(rule->replacement, allocator); + if (NULL == rule_out->replacement) { + goto fail; + } + } + return RCL_RET_OK; +fail: + if (RCL_RET_OK != rcl_remap_fini(rule_out)) { + RCL_SET_ERROR_MSG("Error while finalizing remap rule due to another error", error_alloc); + } + return RCL_RET_BAD_ALLOC; +} + rcl_ret_t rcl_remap_fini( rcl_remap_t * rule) diff --git a/rcl/src/rcl/remap_impl.h b/rcl/src/rcl/remap_impl.h index cb073a936..6f3d05a50 100644 --- a/rcl/src/rcl/remap_impl.h +++ b/rcl/src/rcl/remap_impl.h @@ -51,6 +51,34 @@ typedef struct rcl_remap_t rcl_remap_t rcl_remap_get_zero_initialized(); +/// Copy one remap structure into another. +/** + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | Yes + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] error_alloc an alocator to use if an error occurs. + * This allocator is not used to allocate rule_out. + * \param[in] rule The structure to be copied. + * Its allocator is used to copy memory into the new structure. + * \param[out] rule_out A zero-initialized rcl_remap_t structure to be copied into. + * \return `RCL_RET_OK` if the structure was copied successfully, or + * \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or + * \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or + * \return `RCL_RET_ERROR` if an unspecified error occurs. + */ +RCL_PUBLIC +RCL_WARN_UNUSED +rcl_ret_t +rcl_remap_copy( + rcl_allocator_t error_alloc, + const rcl_remap_t * rule, + rcl_remap_t * rule_out); + /// Reclaim resources used in an rcl_remap_t structure. /** *
diff --git a/rcl/test/rcl/test_arguments.cpp b/rcl/test/rcl/test_arguments.cpp index 0f698f91e..f4b9b2f5a 100644 --- a/rcl/test/rcl/test_arguments.cpp +++ b/rcl/test/rcl/test_arguments.cpp @@ -165,6 +165,26 @@ TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), test_mix_valid_inval EXPECT_EQ(RCL_RET_OK, rcl_arguments_fini(&parsed_args)); } +TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), test_copy) { + const char * argv[] = {"process_name", "/foo/bar:=", "bar:=/fiz/buz", "__ns:=/foo"}; + int argc = sizeof(argv) / sizeof(const char *); + rcl_arguments_t parsed_args = rcl_get_zero_initialized_arguments(); + rcl_ret_t ret; + + ret = rcl_parse_arguments(argc, argv, rcl_get_default_allocator(), &parsed_args); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + + rcl_arguments_t copied_args = rcl_get_zero_initialized_arguments(); + ret = rcl_arguments_copy(rcl_get_default_allocator(), &parsed_args, &copied_args); + EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe(); + + EXPECT_UNPARSED(parsed_args, 0, 1); + EXPECT_EQ(RCL_RET_OK, rcl_arguments_fini(&parsed_args)); + + EXPECT_UNPARSED(copied_args, 0, 1); + EXPECT_EQ(RCL_RET_OK, rcl_arguments_fini(&copied_args)); +} + TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), test_two_namespace) { const char * argv[] = {"process_name", "__ns:=/foo/bar", "__ns:=/fiz/buz"}; int argc = sizeof(argv) / sizeof(const char *);