X-Git-Url: http://git.efficios.com/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Flib%2Flttng-ctl%2Flttng-ctl.c;h=085005c3940bd4d1c039996b83857f454a29cbf0;hp=bbd9e85753e4d30ca708f437b3637143d1296836;hb=55c9e7cac24318259d4f2549c97f7577b7b52db4;hpb=b178f53e90c376dd44b020535c32649edef8f80e diff --git a/src/lib/lttng-ctl/lttng-ctl.c b/src/lib/lttng-ctl/lttng-ctl.c index bbd9e8575..085005c39 100644 --- a/src/lib/lttng-ctl/lttng-ctl.c +++ b/src/lib/lttng-ctl/lttng-ctl.c @@ -45,6 +45,7 @@ #include #include #include +#include #include "filter/filter-ast.h" #include "filter/filter-parser.h" @@ -66,9 +67,16 @@ do { \ } while (0) #endif +#define COPY_DOMAIN_PACKED(dst, src) \ +do { \ + struct lttng_domain _tmp_domain; \ + \ + lttng_ctl_copy_lttng_domain(&_tmp_domain, &src); \ + dst = _tmp_domain; \ +} while (0) /* Socket to session daemon for communication */ -static int sessiond_socket; +static int sessiond_socket = -1; static char sessiond_sock_path[PATH_MAX]; /* Variables */ @@ -243,15 +251,13 @@ end: LTTNG_HIDDEN int lttng_check_tracing_group(void) { - struct group *grp_tracing; /* no free(). See getgrnam(3) */ - gid_t *grp_list; + gid_t *grp_list, tracing_gid; int grp_list_size, grp_id, i; int ret = -1; const char *grp_name = tracing_group; /* Get GID of group 'tracing' */ - grp_tracing = getgrnam(grp_name); - if (!grp_tracing) { + if (utils_get_group_id(grp_name, false, &tracing_gid)) { /* If grp_tracing is NULL, the group does not exist. */ goto end; } @@ -276,7 +282,7 @@ int lttng_check_tracing_group(void) } for (i = 0; i < grp_list_size; i++) { - if (grp_list[i] == grp_tracing->gr_gid) { + if (grp_list[i] == tracing_gid) { ret = 1; break; } @@ -425,17 +431,12 @@ error: /* * Connect to the LTTng session daemon. * - * On success, return 0. On error, return -1. + * On success, return the socket's file descriptor. On error, return -1. */ -static int connect_sessiond(void) +LTTNG_HIDDEN int connect_sessiond(void) { int ret; - /* Don't try to connect if already connected. */ - if (connected) { - return 0; - } - ret = set_session_daemon_path(); if (ret < 0) { goto error; @@ -447,15 +448,18 @@ static int connect_sessiond(void) goto error; } - sessiond_socket = ret; - connected = 1; - - return 0; + return ret; error: return -1; } +static void reset_global_sessiond_connection_state(void) +{ + sessiond_socket = -1; + connected = 0; +} + /* * Clean disconnect from the session daemon. * @@ -467,8 +471,7 @@ static int disconnect_sessiond(void) if (connected) { ret = lttcomm_close_unix_sock(sessiond_socket); - sessiond_socket = 0; - connected = 0; + reset_global_sessiond_connection_state(); } return ret; @@ -543,6 +546,9 @@ int lttng_ctl_ask_sessiond_fds_varlen(struct lttcomm_session_msg *lsm, if (ret < 0) { ret = -LTTNG_ERR_NO_SESSIOND; goto end; + } else { + sessiond_socket = ret; + connected = 1; } /* Send command to session daemon */ @@ -654,7 +660,7 @@ int lttng_register_consumer(struct lttng_handle *handle, lsm.cmd_type = LTTNG_REGISTER_CONSUMER; lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); lttng_ctl_copy_string(lsm.u.reg.path, socket_path, sizeof(lsm.u.reg.path)); @@ -725,7 +731,7 @@ static int _lttng_stop_tracing(const char *session_name, int wait) * call returned value indicates availability. */ if (data_ret) { - usleep(DEFAULT_DATA_AVAILABILITY_WAIT_TIME); + usleep(DEFAULT_DATA_AVAILABILITY_WAIT_TIME_US); } } while (data_ret != 0); @@ -785,7 +791,7 @@ int lttng_add_context(struct lttng_handle *handle, sizeof(lsm.u.context.channel_name)); } - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); @@ -1080,7 +1086,11 @@ int lttng_enable_event_with_exclusions(struct lttng_handle *handle, unsigned int free_filter_expression = 0; struct filter_parser_ctx *ctx = NULL; - memset(&send_buffer, 0, sizeof(send_buffer)); + /* + * We have either a filter or some exclusions, so we need to set up + * a variable-length memory block from where to send the data. + */ + lttng_dynamic_buffer_init(&send_buffer); /* * Cast as non-const since we may replace the filter expression @@ -1121,8 +1131,7 @@ int lttng_enable_event_with_exclusions(struct lttng_handle *handle, lttng_ctl_copy_string(ev->name, "*", sizeof(ev->name)); } - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); - /* FIXME: copying non-packed struct to packed struct. */ + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); memcpy(&lsm.u.enable.event, ev, sizeof(lsm.u.enable.event)); lttng_ctl_copy_string(lsm.session.name, handle->session_name, @@ -1130,12 +1139,6 @@ int lttng_enable_event_with_exclusions(struct lttng_handle *handle, lsm.u.enable.exclusion_count = exclusion_count; lsm.u.enable.bytecode_len = 0; - /* - * We have either a filter or some exclusions, so we need to set up - * a variable-length memory block from where to send the data. - */ - lttng_dynamic_buffer_init(&send_buffer); - /* Parse filter expression. */ if (filter_expression != NULL || handle->domain.type == LTTNG_DOMAIN_JUL || handle->domain.type == LTTNG_DOMAIN_LOG4J @@ -1321,8 +1324,7 @@ int lttng_disable_event_ext(struct lttng_handle *handle, lsm.cmd_type = LTTNG_DISABLE_EVENT; - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); - /* FIXME: copying non-packed struct to packed struct. */ + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); memcpy(&lsm.u.disable.event, ev, sizeof(lsm.u.disable.event)); lttng_ctl_copy_string(lsm.session.name, handle->session_name, @@ -1568,7 +1570,7 @@ int lttng_enable_channel(struct lttng_handle *handle, } lsm.cmd_type = LTTNG_ENABLE_CHANNEL; - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); @@ -1596,7 +1598,7 @@ int lttng_disable_channel(struct lttng_handle *handle, const char *name) lttng_ctl_copy_string(lsm.u.disable.channel_name, name, sizeof(lsm.u.disable.channel_name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); @@ -1604,13 +1606,14 @@ int lttng_disable_channel(struct lttng_handle *handle, const char *name) return lttng_ctl_ask_sessiond(&lsm, NULL); } -/* - * Add PID to session tracker. - * Return 0 on success else a negative LTTng error code. - */ -int lttng_track_pid(struct lttng_handle *handle, int pid) +static int lttng_track_untrack_id(struct lttng_handle *handle, + enum lttng_tracker_type tracker_type, + const struct lttng_tracker_id *id, + enum lttcomm_sessiond_command cmd) { struct lttcomm_session_msg lsm; + char *var_data = NULL; + size_t var_data_len = 0; /* NULL arguments are forbidden. No default values. */ if (handle == NULL) { @@ -1619,41 +1622,80 @@ int lttng_track_pid(struct lttng_handle *handle, int pid) memset(&lsm, 0, sizeof(lsm)); - lsm.cmd_type = LTTNG_TRACK_PID; - lsm.u.pid_tracker.pid = pid; + lsm.cmd_type = cmd; + lsm.u.id_tracker.tracker_type = tracker_type; + lsm.u.id_tracker.id_type = id->type; + switch (id->type) { + case LTTNG_ID_ALL: + break; + case LTTNG_ID_VALUE: + lsm.u.id_tracker.u.value = id->value; + break; + case LTTNG_ID_STRING: + var_data = id->string; + var_data_len = strlen(var_data) + 1; /* Includes \0. */ + lsm.u.id_tracker.u.var_len = var_data_len; + break; + default: + return -LTTNG_ERR_INVALID; + } - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); - return lttng_ctl_ask_sessiond(&lsm, NULL); + return lttng_ctl_ask_sessiond_varlen_no_cmd_header( + &lsm, var_data, var_data_len, NULL); } /* - * Remove PID from session tracker. + * Add ID to session tracker. * Return 0 on success else a negative LTTng error code. */ -int lttng_untrack_pid(struct lttng_handle *handle, int pid) +int lttng_track_id(struct lttng_handle *handle, + enum lttng_tracker_type tracker_type, + const struct lttng_tracker_id *id) { - struct lttcomm_session_msg lsm; - - /* NULL arguments are forbidden. No default values. */ - if (handle == NULL) { - return -LTTNG_ERR_INVALID; - } + return lttng_track_untrack_id(handle, tracker_type, id, LTTNG_TRACK_ID); +} - memset(&lsm, 0, sizeof(lsm)); +/* + * Remove ID from session tracker. + * Return 0 on success else a negative LTTng error code. + */ +int lttng_untrack_id(struct lttng_handle *handle, + enum lttng_tracker_type tracker_type, + const struct lttng_tracker_id *id) +{ + return lttng_track_untrack_id( + handle, tracker_type, id, LTTNG_UNTRACK_ID); +} - lsm.cmd_type = LTTNG_UNTRACK_PID; - lsm.u.pid_tracker.pid = pid; +/* + * Add PID to session tracker. + * Return 0 on success else a negative LTTng error code. + */ +int lttng_track_pid(struct lttng_handle *handle, int pid) +{ + struct lttng_tracker_id id; - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + id.type = LTTNG_TRACKER_PID; + id.value = pid; + return lttng_track_id(handle, LTTNG_TRACKER_PID, &id); +} - lttng_ctl_copy_string(lsm.session.name, handle->session_name, - sizeof(lsm.session.name)); +/* + * Remove PID from session tracker. + * Return 0 on success else a negative LTTng error code. + */ +int lttng_untrack_pid(struct lttng_handle *handle, int pid) +{ + struct lttng_tracker_id id; - return lttng_ctl_ask_sessiond(&lsm, NULL); + id.type = LTTNG_TRACKER_PID; + id.value = pid; + return lttng_untrack_id(handle, LTTNG_TRACKER_PID, &id); } /* @@ -1674,7 +1716,7 @@ int lttng_list_tracepoints(struct lttng_handle *handle, memset(&lsm, 0, sizeof(lsm)); lsm.cmd_type = LTTNG_LIST_TRACEPOINTS; - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); ret = lttng_ctl_ask_sessiond(&lsm, (void **) events); if (ret < 0) { @@ -1702,7 +1744,7 @@ int lttng_list_tracepoint_fields(struct lttng_handle *handle, memset(&lsm, 0, sizeof(lsm)); lsm.cmd_type = LTTNG_LIST_TRACEPOINT_FIELDS; - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); ret = lttng_ctl_ask_sessiond(&lsm, (void **) fields); if (ret < 0) { @@ -1834,7 +1876,7 @@ end: /* * Create a new session using name and url for destination. * - * Returns LTTNG_OK on success or a negative error code. + * Return 0 on success else a negative LTTng error code. */ int lttng_create_session(const char *name, const char *url) { @@ -1889,7 +1931,7 @@ end: /* * Create a session exclusively used for snapshot. * - * Returns LTTNG_OK on success or a negative error code. + * Return 0 on success else a negative LTTng error code. */ int lttng_create_session_snapshot(const char *name, const char *snapshot_url) { @@ -1965,7 +2007,7 @@ end: /* * Create a session exclusively used for live. * - * Returns LTTNG_OK on success or a negative error code. + * Return 0 on success else a negative LTTng error code. */ int lttng_create_session_live(const char *name, const char *url, unsigned int timer_interval) @@ -1997,45 +2039,49 @@ end: return ret; } -/* - * Destroy session using name. - * Returns size of returned session payload data or a negative error code. - */ -static -int _lttng_destroy_session(const char *session_name) -{ - struct lttcomm_session_msg lsm; - - if (session_name == NULL) { - return -LTTNG_ERR_INVALID; - } - - memset(&lsm, 0, sizeof(lsm)); - lsm.cmd_type = LTTNG_DESTROY_SESSION; - - lttng_ctl_copy_string(lsm.session.name, session_name, - sizeof(lsm.session.name)); - - return lttng_ctl_ask_sessiond(&lsm, NULL); -} - /* * Stop the session and wait for the data before destroying it + * + * Return 0 on success else a negative LTTng error code. */ int lttng_destroy_session(const char *session_name) { int ret; + enum lttng_error_code ret_code; + enum lttng_destruction_handle_status status; + struct lttng_destruction_handle *handle = NULL; /* - * Stop the tracing and wait for the data. + * Stop the tracing and wait for the data to be + * consumed. */ ret = _lttng_stop_tracing(session_name, 1); if (ret && ret != -LTTNG_ERR_TRACE_ALREADY_STOPPED) { goto end; } - ret = _lttng_destroy_session(session_name); + ret_code = lttng_destroy_session_ext(session_name, &handle); + if (ret_code != LTTNG_OK) { + ret = (int) -ret_code; + goto end; + } + assert(handle); + + /* Block until the completion of the destruction of the session. */ + status = lttng_destruction_handle_wait_for_completion(handle, -1); + if (status != LTTNG_DESTRUCTION_HANDLE_STATUS_COMPLETED) { + ret = -LTTNG_ERR_UNK; + goto end; + } + + status = lttng_destruction_handle_get_result(handle, &ret_code); + if (status != LTTNG_DESTRUCTION_HANDLE_STATUS_OK) { + ret = -LTTNG_ERR_UNK; + goto end; + } + ret = ret_code == LTTNG_OK ? 0 : -ret_code; end: + lttng_destruction_handle_destroy(handle); return ret; } @@ -2044,21 +2090,10 @@ end: */ int lttng_destroy_session_no_wait(const char *session_name) { - int ret; - - /* - * Stop the tracing without waiting for the data. - * The session might already have been stopped, so just - * skip this error. - */ - ret = _lttng_stop_tracing(session_name, 0); - if (ret && ret != -LTTNG_ERR_TRACE_ALREADY_STOPPED) { - goto end; - } + enum lttng_error_code ret_code; - ret = _lttng_destroy_session(session_name); -end: - return ret; + ret_code = lttng_destroy_session_ext(session_name, NULL); + return ret_code == LTTNG_OK ? ret_code : -ret_code; } /* @@ -2081,7 +2116,6 @@ int lttng_list_sessions(struct lttng_session **out_sessions) lsm.cmd_type = LTTNG_LIST_SESSIONS; ret = lttng_ctl_ask_sessiond(&lsm, (void**) &sessions); if (ret <= 0) { - ret = ret == 0 ? -LTTNG_ERR_FATAL : ret; goto end; } if (!sessions) { @@ -2212,7 +2246,7 @@ int lttng_list_channels(struct lttng_handle *handle, lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); ret = lttng_ctl_ask_sessiond(&lsm, (void**) channels); if (ret < 0) { @@ -2272,7 +2306,7 @@ int lttng_list_events(struct lttng_handle *handle, sizeof(lsm.session.name)); lttng_ctl_copy_string(lsm.u.list.channel_name, channel_name, sizeof(lsm.u.list.channel_name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); ret = lttng_ctl_ask_sessiond_fds_varlen(&lsm, NULL, 0, NULL, 0, (void **) &reception_buffer, (void **) &cmd_header, @@ -2289,7 +2323,7 @@ int lttng_list_events(struct lttng_handle *handle, /* Set number of events and free command header */ nb_events = cmd_header->nb_events; if (nb_events > INT_MAX) { - ret = -EOVERFLOW; + ret = -LTTNG_ERR_OVERFLOW; goto end; } free(cmd_header); @@ -2799,7 +2833,7 @@ int lttng_set_consumer_url(struct lttng_handle *handle, lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); size = uri_parse_str_urls(control_url, data_url, &uris); if (size < 0) { @@ -2818,6 +2852,7 @@ int lttng_set_consumer_url(struct lttng_handle *handle, /* * [OBSOLETE] */ +int lttng_enable_consumer(struct lttng_handle *handle); int lttng_enable_consumer(struct lttng_handle *handle) { return -ENOSYS; @@ -2826,6 +2861,7 @@ int lttng_enable_consumer(struct lttng_handle *handle) /* * [OBSOLETE] */ +int lttng_disable_consumer(struct lttng_handle *handle); int lttng_disable_consumer(struct lttng_handle *handle) { return -ENOSYS; @@ -2834,6 +2870,8 @@ int lttng_disable_consumer(struct lttng_handle *handle) /* * [OBSOLETE] */ +int _lttng_create_session_ext(const char *name, const char *url, + const char *datetime); int _lttng_create_session_ext(const char *name, const char *url, const char *datetime) { @@ -2868,6 +2906,10 @@ int lttng_data_pending(const char *session_name) /* Unexpected payload size */ ret = -LTTNG_ERR_INVALID; goto end; + } else if (!pending) { + /* Internal error. */ + ret = -LTTNG_ERR_UNK; + goto end; } ret = (int) *pending; @@ -2877,52 +2919,159 @@ end: } /* - * List PIDs in the tracker. + * List IDs in the tracker. * - * enabled is set to whether the PID tracker is enabled. - * pids is set to an allocated array of PIDs currently tracked. On - * success, pids must be freed by the caller. - * nr_pids is set to the number of entries contained by the pids array. + * tracker_type is the type of tracker. + * ids is set to an allocated array of IDs currently tracked. On + * success, ids and all the strings it contains must be freed by the caller. + * nr_ids is set to the number of entries contained by the ids array. * * Returns 0 on success, else a negative LTTng error code. */ -int lttng_list_tracker_pids(struct lttng_handle *handle, - int *_enabled, int32_t **_pids, size_t *_nr_pids) +int lttng_list_tracker_ids(struct lttng_handle *handle, + enum lttng_tracker_type tracker_type, + struct lttng_tracker_id **_ids, + size_t *_nr_ids) { - int ret; - int enabled = 1; + int ret, i; struct lttcomm_session_msg lsm; - size_t nr_pids; - int32_t *pids = NULL; + struct lttcomm_tracker_command_header *cmd_header = NULL; + char *cmd_payload = NULL, *p; + size_t cmd_header_len; + size_t nr_ids = 0; + struct lttng_tracker_id *ids = NULL; if (handle == NULL) { return -LTTNG_ERR_INVALID; } memset(&lsm, 0, sizeof(lsm)); - lsm.cmd_type = LTTNG_LIST_TRACKER_PIDS; + lsm.cmd_type = LTTNG_LIST_TRACKER_IDS; + lsm.u.id_tracker_list.tracker_type = tracker_type; lttng_ctl_copy_string(lsm.session.name, handle->session_name, sizeof(lsm.session.name)); - lttng_ctl_copy_lttng_domain(&lsm.domain, &handle->domain); + COPY_DOMAIN_PACKED(lsm.domain, handle->domain); - ret = lttng_ctl_ask_sessiond(&lsm, (void **) &pids); + ret = lttng_ctl_ask_sessiond_fds_varlen(&lsm, NULL, 0, NULL, 0, + (void **) &cmd_payload, (void **) &cmd_header, + &cmd_header_len); if (ret < 0) { + goto error; + } + + /* Set number of tracker_id and free command header */ + nr_ids = cmd_header->nb_tracker_id; + if (nr_ids > INT_MAX) { + ret = -LTTNG_ERR_OVERFLOW; + goto error; + } + free(cmd_header); + cmd_header = NULL; + + ids = zmalloc(sizeof(*ids) * nr_ids); + if (!ids) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + p = cmd_payload; + for (i = 0; i < nr_ids; i++) { + struct lttcomm_tracker_id_header *tracker_id; + struct lttng_tracker_id *id; + + tracker_id = (struct lttcomm_tracker_id_header *) p; + p += sizeof(struct lttcomm_tracker_id_header); + id = &ids[i]; + + id->type = tracker_id->type; + switch (tracker_id->type) { + case LTTNG_ID_ALL: + break; + case LTTNG_ID_VALUE: + id->value = tracker_id->u.value; + break; + case LTTNG_ID_STRING: + id->string = strdup(p); + if (!id->string) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + p += tracker_id->u.var_data_len; + break; + default: + goto error; + } + } + free(cmd_payload); + *_ids = ids; + *_nr_ids = nr_ids; + return 0; + +error: + if (ids) { + for (i = 0; i < nr_ids; i++) { + free(ids[i].string); + } + free(ids); + } + free(cmd_payload); + free(cmd_header); + return ret; +} + +/* + * List PIDs in the tracker. + * + * enabled is set to whether the PID tracker is enabled. + * pids is set to an allocated array of PIDs currently tracked. On + * success, pids must be freed by the caller. + * nr_pids is set to the number of entries contained by the pids array. + * + * Returns 0 on success, else a negative LTTng error code. + */ +int lttng_list_tracker_pids(struct lttng_handle *handle, + int *_enabled, int32_t **_pids, size_t *_nr_pids) +{ + struct lttng_tracker_id *ids = NULL; + size_t nr_ids = 0; + int *pids = NULL; + int ret = 0, i; + + ret = lttng_list_tracker_ids(handle, LTTNG_TRACKER_PID, &ids, &nr_ids); + if (ret < 0) return ret; + + if (nr_ids == 1 && ids[0].type == LTTNG_ID_ALL) { + *_enabled = 0; + goto end; } - nr_pids = ret / sizeof(int32_t); - if (nr_pids > 0 && !pids) { - return -LTTNG_ERR_UNK; + *_enabled = 1; + + pids = zmalloc(nr_ids * sizeof(*pids)); + if (!pids) { + ret = -LTTNG_ERR_NOMEM; + goto end; } - if (nr_pids == 1 && pids[0] == -1) { - free(pids); - pids = NULL; - enabled = 0; - nr_pids = 0; + for (i = 0; i < nr_ids; i++) { + struct lttng_tracker_id *id = &ids[i]; + + if (id->type != LTTNG_ID_VALUE) { + ret = -LTTNG_ERR_UNK; + goto end; + } + pids[i] = id->value; } - *_enabled = enabled; *_pids = pids; - *_nr_pids = nr_pids; - return 0; + *_nr_pids = nr_ids; +end: + for (i = 0; i < nr_ids; i++) { + free(ids[i].string); + } + free(ids); + if (ret < 0) { + free(pids); + } + return ret; } /*