+BT_HIDDEN
+struct ctf_fs_trace *ctf_fs_trace_create(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_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(ctf_fs_trace, metadata_config);
+ if (ret) {
+ goto error;
+ }
+
+ ret = create_ds_file_groups(ctf_fs_trace);
+ if (ret) {
+ goto error;
+ }
+
+ ret = create_cc_prio_map(ctf_fs_trace);
+ if (ret) {
+ goto error;
+ }
+
+ /*
+ * create_ds_file_groups() created all the streams that this
+ * trace needs. There won't be any more. Therefore it is safe to
+ * make this trace static.
+ */
+ (void) bt_ctf_trace_set_is_static(ctf_fs_trace->metadata->trace);
+
+ 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);
+ assert(*trace_paths);
+ norm_path = NULL;
+
+end:
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ }
+
+ return ret;
+}
+
+BT_HIDDEN
+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);