+ /*
+ * Check if we have a trace environment string value named `hostname`.
+ * If so, use it as the trace name's prefix.
+ */
+ val = bt_trace_class_borrow_environment_entry_value_by_name_const(
+ tc, "hostname");
+ if (val && bt_value_is_string(val)) {
+ g_string_append(name, bt_value_string_get(val));
+
+ if (name_suffix) {
+ g_string_append_c(name, G_DIR_SEPARATOR);
+ }
+ }
+
+ if (name_suffix) {
+ g_string_append(name, name_suffix);
+ }
+
+ ret = bt_trace_set_name(trace, name->str);
+ if (ret) {
+ goto end;
+ }
+
+ goto end;
+
+end:
+ if (name) {
+ g_string_free(name, TRUE);
+ }
+
+ return ret;
+}
+
+static
+struct ctf_fs_trace *ctf_fs_trace_create(bt_self_component_source *self_comp,
+ const char *path, const char *name,
+ struct ctf_fs_metadata_config *metadata_config)
+{
+ struct ctf_fs_trace *ctf_fs_trace;
+ int ret;
+
+ ctf_fs_trace = g_new0(struct ctf_fs_trace, 1);
+ if (!ctf_fs_trace) {
+ goto end;
+ }
+
+ ctf_fs_trace->path = g_string_new(path);
+ if (!ctf_fs_trace->path) {
+ goto error;
+ }
+
+ ctf_fs_trace->name = g_string_new(name);
+ if (!ctf_fs_trace->name) {
+ goto error;
+ }
+
+ ctf_fs_trace->metadata = g_new0(struct ctf_fs_metadata, 1);
+ if (!ctf_fs_trace->metadata) {
+ goto error;
+ }
+
+ ctf_fs_metadata_init(ctf_fs_trace->metadata);
+ ctf_fs_trace->ds_file_groups = g_ptr_array_new_with_free_func(
+ (GDestroyNotify) ctf_fs_ds_file_group_destroy);
+ if (!ctf_fs_trace->ds_file_groups) {
+ goto error;
+ }
+
+ ret = ctf_fs_metadata_set_trace_class(self_comp,
+ ctf_fs_trace, metadata_config);
+ if (ret) {
+ goto error;
+ }
+
+ if (ctf_fs_trace->metadata->trace_class) {
+ ctf_fs_trace->trace =
+ bt_trace_create(ctf_fs_trace->metadata->trace_class);
+ if (!ctf_fs_trace->trace) {
+ goto error;
+ }
+ }
+
+ if (ctf_fs_trace->trace) {
+ ret = set_trace_name(ctf_fs_trace->trace, name);
+ if (ret) {
+ goto error;
+ }
+ }
+
+ ret = create_ds_file_groups(ctf_fs_trace);
+ if (ret) {
+ goto error;
+ }
+
+ goto end;
+
+error:
+ ctf_fs_trace_destroy(ctf_fs_trace);
+ ctf_fs_trace = NULL;
+
+end:
+ return ctf_fs_trace;
+}
+
+static
+int path_is_ctf_trace(const char *path)
+{
+ GString *metadata_path = g_string_new(NULL);
+ int ret = 0;
+
+ if (!metadata_path) {
+ ret = -1;
+ goto end;
+ }
+
+ g_string_printf(metadata_path, "%s" G_DIR_SEPARATOR_S "%s", path, CTF_FS_METADATA_FILENAME);
+
+ if (g_file_test(metadata_path->str, G_FILE_TEST_IS_REGULAR)) {
+ ret = 1;
+ goto end;
+ }
+
+end:
+ g_string_free(metadata_path, TRUE);
+ return ret;
+}
+
+static
+int add_trace_path(GList **trace_paths, const char *path)
+{
+ GString *norm_path = NULL;
+ int ret = 0;
+
+ norm_path = bt_common_normalize_path(path, NULL);
+ if (!norm_path) {
+ BT_LOGE("Failed to normalize path `%s`.", path);
+ ret = -1;
+ goto end;
+ }
+
+ // FIXME: Remove or ifdef for __MINGW32__
+ if (strcmp(norm_path->str, "/") == 0) {
+ BT_LOGE("Opening a trace in `/` is not supported.");
+ ret = -1;
+ goto end;
+ }
+
+ *trace_paths = g_list_prepend(*trace_paths, norm_path);
+ BT_ASSERT(*trace_paths);
+ norm_path = NULL;
+
+end:
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ }
+
+ return ret;
+}
+
+static
+int ctf_fs_find_traces(GList **trace_paths, const char *start_path)
+{
+ int ret;
+ GError *error = NULL;
+ GDir *dir = NULL;
+ const char *basename = NULL;
+
+ /* Check if the starting path is a CTF trace itself */
+ ret = path_is_ctf_trace(start_path);
+ if (ret < 0) {
+ goto end;
+ }
+
+ if (ret) {
+ /*
+ * Stop recursion: a CTF trace cannot contain another
+ * CTF trace.
+ */
+ ret = add_trace_path(trace_paths, start_path);
+ goto end;
+ }
+
+ /* Look for subdirectories */
+ if (!g_file_test(start_path, G_FILE_TEST_IS_DIR)) {
+ /* Starting path is not a directory: end of recursion */
+ goto end;
+ }
+
+ dir = g_dir_open(start_path, 0, &error);
+ if (!dir) {
+ if (error->code == G_FILE_ERROR_ACCES) {
+ BT_LOGD("Cannot open directory `%s`: %s (code %d): continuing",
+ start_path, error->message, error->code);
+ goto end;
+ }
+
+ BT_LOGE("Cannot open directory `%s`: %s (code %d)",
+ start_path, error->message, error->code);
+ ret = -1;
+ goto end;
+ }
+
+ while ((basename = g_dir_read_name(dir))) {
+ GString *sub_path = g_string_new(NULL);
+
+ if (!sub_path) {
+ ret = -1;
+ goto end;
+ }
+
+ g_string_printf(sub_path, "%s" G_DIR_SEPARATOR_S "%s", start_path, basename);
+ ret = ctf_fs_find_traces(trace_paths, sub_path->str);
+ g_string_free(sub_path, TRUE);
+ if (ret) {
+ goto end;
+ }
+ }
+
+end:
+ if (dir) {
+ g_dir_close(dir);
+ }
+
+ if (error) {
+ g_error_free(error);
+ }
+
+ return ret;
+}
+
+static
+GList *ctf_fs_create_trace_names(GList *trace_paths, const char *base_path) {
+ GList *trace_names = NULL;
+ GList *node;
+ const char *last_sep;
+ size_t base_dist;
+
+ /*
+ * At this point we know that all the trace paths are
+ * normalized, and so is the base path. This means that
+ * they are absolute and they don't end with a separator.
+ * We can simply find the location of the last separator
+ * in the base path, which gives us the name of the actual
+ * directory to look into, and use this location as the
+ * start of each trace name within each trace path.
+ *
+ * For example:
+ *
+ * Base path: /home/user/my-traces/some-trace
+ * Trace paths:
+ * - /home/user/my-traces/some-trace/host1/trace1
+ * - /home/user/my-traces/some-trace/host1/trace2
+ * - /home/user/my-traces/some-trace/host2/trace
+ * - /home/user/my-traces/some-trace/other-trace
+ *
+ * In this case the trace names are:
+ *
+ * - some-trace/host1/trace1
+ * - some-trace/host1/trace2
+ * - some-trace/host2/trace
+ * - some-trace/other-trace
+ */
+ last_sep = strrchr(base_path, G_DIR_SEPARATOR);
+
+ /* We know there's at least one separator */
+ BT_ASSERT(last_sep);
+
+ /* Distance to base */
+ base_dist = last_sep - base_path + 1;
+
+ /* Create the trace names */
+ for (node = trace_paths; node; node = g_list_next(node)) {
+ GString *trace_name = g_string_new(NULL);
+ GString *trace_path = node->data;
+
+ BT_ASSERT(trace_name);
+ g_string_assign(trace_name, &trace_path->str[base_dist]);
+ trace_names = g_list_append(trace_names, trace_name);
+ }
+
+ return trace_names;
+}
+
+/* Helper for ctf_fs_component_create_ctf_fs_traces, to handle a single path/root. */
+
+static
+int ctf_fs_component_create_ctf_fs_traces_one_root(bt_self_component_source *self_comp,
+ struct ctf_fs_component *ctf_fs,
+ const char *path_param)
+{
+ struct ctf_fs_trace *ctf_fs_trace = NULL;
+ int ret = 0;
+ GString *norm_path = NULL;
+ GList *trace_paths = NULL;
+ GList *trace_names = NULL;
+ GList *tp_node;
+ GList *tn_node;
+
+ norm_path = bt_common_normalize_path(path_param, NULL);
+ if (!norm_path) {
+ BT_LOGE("Failed to normalize path: `%s`.",
+ path_param);
+ goto error;
+ }
+
+ ret = ctf_fs_find_traces(&trace_paths, norm_path->str);
+ if (ret) {
+ goto error;
+ }
+
+ if (!trace_paths) {
+ BT_LOGE("No CTF traces recursively found in `%s`.",
+ path_param);
+ goto error;
+ }
+
+ trace_names = ctf_fs_create_trace_names(trace_paths, norm_path->str);
+ if (!trace_names) {
+ BT_LOGE("Cannot create trace names from trace paths.");
+ goto error;
+ }
+
+ for (tp_node = trace_paths, tn_node = trace_names; tp_node;
+ tp_node = g_list_next(tp_node),
+ tn_node = g_list_next(tn_node)) {
+ GString *trace_path = tp_node->data;
+ GString *trace_name = tn_node->data;
+
+ ctf_fs_trace = ctf_fs_trace_create(self_comp,
+ trace_path->str, trace_name->str,
+ &ctf_fs->metadata_config);
+ if (!ctf_fs_trace) {
+ BT_LOGE("Cannot create trace for `%s`.",
+ trace_path->str);
+ goto error;
+ }
+
+ g_ptr_array_add(ctf_fs->traces, ctf_fs_trace);
+ ctf_fs_trace = NULL;
+ }
+
+ goto end;
+
+error:
+ ret = -1;
+ ctf_fs_trace_destroy(ctf_fs_trace);
+
+end:
+ for (tp_node = trace_paths; tp_node; tp_node = g_list_next(tp_node)) {
+ if (tp_node->data) {
+ g_string_free(tp_node->data, TRUE);
+ }
+ }
+
+ for (tn_node = trace_names; tn_node; tn_node = g_list_next(tn_node)) {
+ if (tn_node->data) {
+ g_string_free(tn_node->data, TRUE);
+ }
+ }
+
+ if (trace_paths) {
+ g_list_free(trace_paths);
+ }
+
+ if (trace_names) {
+ g_list_free(trace_names);
+ }
+
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ }
+
+ return ret;
+}
+
+/* GCompareFunc to sort traces by UUID. */
+
+static
+gint sort_traces_by_uuid(gconstpointer a, gconstpointer b)
+{
+ const struct ctf_fs_trace *trace_a = *((const struct ctf_fs_trace **) a);
+ const struct ctf_fs_trace *trace_b = *((const struct ctf_fs_trace **) b);
+
+ bool trace_a_has_uuid = trace_a->metadata->tc->is_uuid_set;
+ bool trace_b_has_uuid = trace_b->metadata->tc->is_uuid_set;
+ gint ret;
+
+ /* Order traces without uuid first. */
+ if (!trace_a_has_uuid && trace_b_has_uuid) {
+ ret = -1;
+ } else if (trace_a_has_uuid && !trace_b_has_uuid) {
+ ret = 1;
+ } else if (!trace_a_has_uuid && !trace_b_has_uuid) {
+ ret = 0;
+ } else {
+ ret = bt_uuid_compare(trace_a->metadata->tc->uuid, trace_b->metadata->tc->uuid);
+ }
+
+ return ret;
+}
+
+/*
+ * Count the number of stream and event classes defined by this trace's metadata.
+ *
+ * This is used to determine which metadata is the "latest", out of multiple
+ * traces sharing the same UUID. It is assumed that amongst all these metadatas,
+ * a bigger metadata is a superset of a smaller metadata. Therefore, it is
+ * enough to just count the classes.
+ */
+
+static
+unsigned int metadata_count_stream_and_event_classes(struct ctf_fs_trace *trace)
+{
+ unsigned int num = trace->metadata->tc->stream_classes->len;
+ guint i;
+
+ for (i = 0; i < trace->metadata->tc->stream_classes->len; i++) {
+ struct ctf_stream_class *sc = trace->metadata->tc->stream_classes->pdata[i];
+ num += sc->event_classes->len;
+ }
+
+ return num;
+}
+
+/*
+ * Merge the src ds_file_group into dest. This consists of merging their
+ * ds_file_infos, making sure to keep the result sorted.
+ */
+
+static
+void merge_ctf_fs_ds_file_groups(struct ctf_fs_ds_file_group *dest, struct ctf_fs_ds_file_group *src)
+{
+ guint i;
+
+ for (i = 0; i < src->ds_file_infos->len; i++) {
+ struct ctf_fs_ds_file_info *ds_file_info =
+ g_ptr_array_index(src->ds_file_infos, i);
+
+ /* Ownership of the ds_file_info is transferred to dest. */
+ g_ptr_array_index(src->ds_file_infos, i) = NULL;
+
+ ds_file_group_insert_ds_file_info_sorted(dest, ds_file_info);
+ }
+
+ /* Merge both indexes. */
+ for (i = 0; i < src->index->entries->len; i++) {
+ struct ctf_fs_ds_index_entry *entry = g_ptr_array_index(
+ src->index->entries, i);
+
+ /*
+ * Ownership of the ctf_fs_ds_index_entry is transferred to
+ * dest.
+ */
+ g_ptr_array_index(src->index->entries, i) = NULL;
+
+ ds_file_group_insert_ds_index_entry_sorted(dest, entry);
+ }
+}
+/* Merge src_trace's data stream file groups into dest_trace's. */
+
+static
+int merge_matching_ctf_fs_ds_file_groups(
+ struct ctf_fs_trace *dest_trace,
+ struct ctf_fs_trace *src_trace)
+{
+
+ GPtrArray *dest = dest_trace->ds_file_groups;
+ GPtrArray *src = src_trace->ds_file_groups;
+ guint s_i;
+ int ret = 0;
+
+ /*
+ * Save the initial length of dest: we only want to check against the
+ * original elements in the inner loop.
+ */
+ const guint dest_len = dest->len;
+
+ for (s_i = 0; s_i < src->len; s_i++) {
+ struct ctf_fs_ds_file_group *src_group = g_ptr_array_index(src, s_i);
+ struct ctf_fs_ds_file_group *dest_group = NULL;
+
+ /* A stream instance without ID can't match a stream in the other trace. */
+ if (src_group->stream_id != -1) {
+ guint d_i;
+
+ /* Let's search for a matching ds_file_group in the destination. */
+ for (d_i = 0; d_i < dest_len; d_i++) {
+ struct ctf_fs_ds_file_group *candidate_dest = g_ptr_array_index(dest, d_i);
+
+ /* Can't match a stream instance without ID. */
+ if (candidate_dest->stream_id == -1) {
+ continue;
+ }
+
+ /*
+ * If the two groups have the same stream instance id
+ * and belong to the same stream class (stream instance
+ * ids are per-stream class), they represent the same
+ * stream instance.
+ */
+ if (candidate_dest->stream_id != src_group->stream_id ||
+ candidate_dest->sc->id != src_group->sc->id) {
+ continue;
+ }
+
+ dest_group = candidate_dest;
+ break;
+ }
+ }
+
+ /*
+ * Didn't find a friend in dest to merge our src_group into?
+ * Create a new empty one. This can happen if a stream was
+ * active in the source trace chunk but not in the destination
+ * trace chunk.
+ */
+ if (!dest_group) {
+ struct ctf_stream_class *sc;
+ struct ctf_fs_ds_index *index;
+
+ sc = ctf_trace_class_borrow_stream_class_by_id(
+ dest_trace->metadata->tc, src_group->sc->id);
+ BT_ASSERT(sc);
+
+ index = ctf_fs_ds_index_create();
+ if (!index) {
+ ret = -1;
+ goto end;
+ }
+
+ dest_group = ctf_fs_ds_file_group_create(dest_trace, sc,
+ src_group->stream_id, index);
+ /* Ownership of index is transferred. */
+ index = NULL;
+ if (!dest_group) {
+ ret = -1;
+ goto end;
+ }
+
+ g_ptr_array_add(dest_trace->ds_file_groups, dest_group);
+ }
+
+ BT_ASSERT(dest_group);
+ merge_ctf_fs_ds_file_groups(dest_group, src_group);
+ }
+
+end:
+ return ret;
+}
+
+/*
+ * Collapse the given traces, which must all share the same UUID, in a single
+ * one.
+ *
+ * The trace with the most expansive metadata is chosen and all other traces
+ * are merged into that one. The array slots of all the traces that get merged
+ * in the chosen one are set to NULL, so only the slot of the chosen trace
+ * remains non-NULL.
+ */
+
+static
+int merge_ctf_fs_traces(struct ctf_fs_trace **traces, unsigned int num_traces)
+{
+ unsigned int winner_count;
+ struct ctf_fs_trace *winner;
+ guint i;
+ int ret = 0;
+ char uuid_str[BABELTRACE_UUID_STR_LEN];
+
+ BT_ASSERT(num_traces >= 2);
+
+ winner_count = metadata_count_stream_and_event_classes(traces[0]);
+ winner = traces[0];
+
+ /* Find the trace with the largest metadata. */
+ for (i = 1; i < num_traces; i++) {
+ struct ctf_fs_trace *candidate;
+ unsigned int candidate_count;
+
+ candidate = traces[i];
+
+ /* A bit of sanity check. */
+ BT_ASSERT(bt_uuid_compare(winner->metadata->tc->uuid, candidate->metadata->tc->uuid) == 0);
+
+ candidate_count = metadata_count_stream_and_event_classes(candidate);
+
+ if (candidate_count > winner_count) {
+ winner_count = candidate_count;
+ winner = candidate;
+ }
+ }
+
+ /* Merge all the other traces in the winning trace. */
+ for (i = 0; i < num_traces; i++) {
+ struct ctf_fs_trace *trace = traces[i];
+
+ /* Don't merge the winner into itself. */
+ if (trace == winner) {
+ continue;
+ }
+
+ /* Merge trace's data stream file groups into winner's. */
+ ret = merge_matching_ctf_fs_ds_file_groups(winner, trace);
+ if (ret) {
+ goto end;
+ }