converter: parse 1.x and 2.0 options
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Mon, 24 Oct 2016 20:27:19 +0000 (16:27 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Sat, 27 May 2017 18:09:06 +0000 (14:09 -0400)
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
converter/Makefile.am
converter/babeltrace-cfg.c [new file with mode: 0644]
converter/babeltrace-cfg.h [new file with mode: 0644]
converter/babeltrace.c

index b7e126458b3c43d5ef09c5d6a71f136c207ef659..5909e2a3a7ff2dc7b412b0567d3b4cde183bc206 100644 (file)
@@ -4,7 +4,9 @@ AM_LDFLAGS = -lpopt
 bin_PROGRAMS = babeltrace babeltrace-log
 
 babeltrace_SOURCES = \
-       babeltrace.c
+       babeltrace.c \
+       babeltrace-cfg.c \
+       babeltrace-cfg.h
 
 # -Wl,--no-as-needed is needed for recent gold linker who seems to think
 # it knows better and considers libraries with constructors having
diff --git a/converter/babeltrace-cfg.c b/converter/babeltrace-cfg.c
new file mode 100644 (file)
index 0000000..ee951bc
--- /dev/null
@@ -0,0 +1,2564 @@
+/*
+ * Babeltrace trace converter - parameter parsing
+ *
+ * Copyright 2016 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/values.h>
+#include <popt.h>
+#include <glib.h>
+#include "babeltrace-cfg.h"
+
+/*
+ * Error printf() macro which prepends "Error: " the first time it's
+ * called. This gives a nicer feel than having a bunch of error prefixes
+ * (since the following lines usually describe the error and possible
+ * solutions), or the error prefix just at the end.
+ */
+#define printf_err(fmt, args...)                       \
+       do {                                            \
+               if (is_first_error) {                   \
+                       fprintf(stderr, "Error: ");     \
+                       is_first_error = false;         \
+               }                                       \
+               fprintf(stderr, fmt, ##args);           \
+       } while (0)
+
+static bool is_first_error = true;
+
+/* INI-style parsing FSM states */
+enum ini_parsing_fsm_state {
+       /* Expect a map key (identifier) */
+       INI_EXPECT_MAP_KEY,
+
+       /* Expect an equal character ('=') */
+       INI_EXPECT_EQUAL,
+
+       /* Expect a value */
+       INI_EXPECT_VALUE,
+
+       /* Expect a negative number value */
+       INI_EXPECT_VALUE_NUMBER_NEG,
+
+       /* Expect a comma character (',') */
+       INI_EXPECT_COMMA,
+};
+
+/* INI-style parsing state variables */
+struct ini_parsing_state {
+       /* Lexical scanner (owned by this) */
+       GScanner *scanner;
+
+       /* Output map value object being filled (owned by this) */
+       struct bt_value *params;
+
+       /* Next expected FSM state */
+       enum ini_parsing_fsm_state expecting;
+
+       /* Last decoded map key (owned by this) */
+       char *last_map_key;
+
+       /* Complete INI-style string to parse (not owned by this) */
+       const char *arg;
+
+       /* Error buffer (not owned by this) */
+       GString *ini_error;
+};
+
+/* Offset option with "is set" boolean */
+struct offset_opt {
+       int64_t value;
+       bool is_set;
+};
+
+/* Legacy "ctf"/"lttng-live" format options */
+struct ctf_legacy_opts {
+       struct offset_opt offset_s;
+       struct offset_opt offset_ns;
+       bool stream_intersection;
+};
+
+/* Legacy "text" format options */
+struct text_legacy_opts {
+       /*
+        * output, dbg_info_dir, dbg_info_target_prefix, names,
+        * and fields are owned by this.
+        */
+       GString *output;
+       GString *dbg_info_dir;
+       GString *dbg_info_target_prefix;
+       struct bt_value *names;
+       struct bt_value *fields;
+
+       /* Flags */
+       bool no_delta;
+       bool clock_cycles;
+       bool clock_seconds;
+       bool clock_date;
+       bool clock_gmt;
+       bool dbg_info_full_path;
+};
+
+/* Legacy input format format */
+enum legacy_input_format {
+       LEGACY_INPUT_FORMAT_NONE = 0,
+       LEGACY_INPUT_FORMAT_CTF,
+       LEGACY_INPUT_FORMAT_LTTNG_LIVE,
+};
+
+/* Legacy output format format */
+enum legacy_output_format {
+       LEGACY_OUTPUT_FORMAT_NONE = 0,
+       LEGACY_OUTPUT_FORMAT_TEXT,
+       LEGACY_OUTPUT_FORMAT_CTF_METADATA,
+       LEGACY_OUTPUT_FORMAT_DUMMY,
+};
+
+/*
+ * Prints the "out of memory" error.
+ */
+static
+void print_err_oom(void)
+{
+       printf_err("Out of memory\n");
+}
+
+/*
+ * Prints duplicate legacy output format error.
+ */
+static
+void print_err_dup_legacy_output(void)
+{
+       printf_err("More than one legacy output format specified\n");
+}
+
+/*
+ * Prints duplicate legacy input format error.
+ */
+static
+void print_err_dup_legacy_input(void)
+{
+       printf_err("More than one legacy input format specified\n");
+}
+
+/*
+ * Checks if any of the "text" legacy options is set.
+ */
+static
+bool text_legacy_opts_is_any_set(struct text_legacy_opts *opts)
+{
+       return (opts->output && opts->output->len > 0) ||
+               (opts->dbg_info_dir && opts->dbg_info_dir->len > 0) ||
+               (opts->dbg_info_target_prefix &&
+                       opts->dbg_info_target_prefix->len > 0) ||
+               bt_value_array_size(opts->names) > 0 ||
+               bt_value_array_size(opts->fields) > 0 ||
+               opts->no_delta || opts->clock_cycles || opts->clock_seconds ||
+               opts->clock_date || opts->clock_gmt ||
+               opts->dbg_info_full_path;
+}
+
+/*
+ * Checks if any of the "ctf" legacy options is set.
+ */
+static
+bool ctf_legacy_opts_is_any_set(struct ctf_legacy_opts *opts)
+{
+       return opts->offset_s.is_set || opts->offset_ns.is_set ||
+               opts->stream_intersection;
+}
+
+/*
+ * Appends an "expecting token" error to the INI-style parsing state's
+ * error buffer.
+ */
+static
+void ini_append_error_expecting(struct ini_parsing_state *state,
+               GScanner *scanner, const char *expecting)
+{
+       size_t i;
+       size_t pos;
+
+       g_string_append_printf(state->ini_error, "Expecting %s:\n", expecting);
+
+       /* Only print error if there's one line */
+       if (strchr(state->arg, '\n') != NULL || strlen(state->arg) == 0) {
+               return;
+       }
+
+       g_string_append_printf(state->ini_error, "\n    %s\n", state->arg);
+       pos = g_scanner_cur_position(scanner) + 4;
+
+       if (!g_scanner_eof(scanner)) {
+               pos--;
+       }
+
+       for (i = 0; i < pos; ++i) {
+               g_string_append_printf(state->ini_error, " ");
+       }
+
+       g_string_append_printf(state->ini_error, "^\n\n");
+}
+
+static
+int ini_handle_state(struct ini_parsing_state *state)
+{
+       int ret = 0;
+       GTokenType token_type;
+       struct bt_value *value = NULL;
+
+       token_type = g_scanner_get_next_token(state->scanner);
+       if (token_type == G_TOKEN_EOF) {
+               if (state->expecting != INI_EXPECT_COMMA) {
+                       switch (state->expecting) {
+                       case INI_EXPECT_EQUAL:
+                               ini_append_error_expecting(state,
+                                       state->scanner, "'='");
+                               break;
+                       case INI_EXPECT_VALUE:
+                       case INI_EXPECT_VALUE_NUMBER_NEG:
+                               ini_append_error_expecting(state,
+                                       state->scanner, "value");
+                               break;
+                       case INI_EXPECT_MAP_KEY:
+                               ini_append_error_expecting(state,
+                                       state->scanner, "unquoted map key");
+                               break;
+                       default:
+                               break;
+                       }
+                       goto error;
+               }
+
+               /* We're done! */
+               ret = 1;
+               goto success;
+       }
+
+       switch (state->expecting) {
+       case INI_EXPECT_MAP_KEY:
+               if (token_type != G_TOKEN_IDENTIFIER) {
+                       ini_append_error_expecting(state, state->scanner,
+                               "unquoted map key");
+                       goto error;
+               }
+
+               free(state->last_map_key);
+               state->last_map_key =
+                       strdup(state->scanner->value.v_identifier);
+               if (!state->last_map_key) {
+                       g_string_append(state->ini_error,
+                               "Out of memory\n");
+                       goto error;
+               }
+
+               if (bt_value_map_has_key(state->params, state->last_map_key)) {
+                       g_string_append_printf(state->ini_error,
+                               "Duplicate parameter key: \"%s\"\n",
+                               state->last_map_key);
+                       goto error;
+               }
+
+               state->expecting = INI_EXPECT_EQUAL;
+               goto success;
+       case INI_EXPECT_EQUAL:
+               if (token_type != G_TOKEN_CHAR) {
+                       ini_append_error_expecting(state,
+                               state->scanner, "'='");
+                       goto error;
+               }
+
+               if (state->scanner->value.v_char != '=') {
+                       ini_append_error_expecting(state,
+                               state->scanner, "'='");
+                       goto error;
+               }
+
+               state->expecting = INI_EXPECT_VALUE;
+               goto success;
+       case INI_EXPECT_VALUE:
+       {
+               switch (token_type) {
+               case G_TOKEN_CHAR:
+                       if (state->scanner->value.v_char == '-') {
+                               /* Negative number */
+                               state->expecting =
+                                       INI_EXPECT_VALUE_NUMBER_NEG;
+                               goto success;
+                       } else {
+                               ini_append_error_expecting(state,
+                                       state->scanner, "value");
+                               goto error;
+                       }
+                       break;
+               case G_TOKEN_INT:
+               {
+                       /* Positive integer */
+                       uint64_t int_val = state->scanner->value.v_int64;
+
+                       if (int_val > (1ULL << 63) - 1) {
+                               g_string_append_printf(state->ini_error,
+                                       "Integer value %" PRIu64 " is outside the range of a 64-bit signed integer\n",
+                                       int_val);
+                               goto error;
+                       }
+
+                       value = bt_value_integer_create_init(
+                               (int64_t) int_val);
+                       break;
+               }
+               case G_TOKEN_FLOAT:
+                       /* Positive floating point number */
+                       value = bt_value_float_create_init(
+                               state->scanner->value.v_float);
+                       break;
+               case G_TOKEN_STRING:
+                       /* Quoted string */
+                       value = bt_value_string_create_init(
+                               state->scanner->value.v_string);
+                       break;
+               case G_TOKEN_IDENTIFIER:
+               {
+                       /*
+                        * Using symbols would be appropriate here,
+                        * but said symbols are allowed as map key,
+                        * so it's easier to consider everything an
+                        * identifier.
+                        *
+                        * If one of the known symbols is not
+                        * recognized here, then fall back to creating
+                        * a string value.
+                        */
+                       const char *id = state->scanner->value.v_identifier;
+
+                       if (!strcmp(id, "null") || !strcmp(id, "NULL") ||
+                                       !strcmp(id, "nul")) {
+                               value = bt_value_null;
+                       } else if (!strcmp(id, "true") || !strcmp(id, "TRUE") ||
+                                       !strcmp(id, "yes") ||
+                                       !strcmp(id, "YES")) {
+                               value = bt_value_bool_create_init(true);
+                       } else if (!strcmp(id, "false") ||
+                                       !strcmp(id, "FALSE") ||
+                                       !strcmp(id, "no") ||
+                                       !strcmp(id, "NO")) {
+                               value = bt_value_bool_create_init(false);
+                       } else {
+                               value = bt_value_string_create_init(id);
+                       }
+                       break;
+               }
+               default:
+                       /* Unset value variable will trigger the error */
+                       break;
+               }
+
+               if (!value) {
+                       ini_append_error_expecting(state,
+                               state->scanner, "value");
+                       goto error;
+               }
+
+               state->expecting = INI_EXPECT_COMMA;
+               goto success;
+       }
+       case INI_EXPECT_VALUE_NUMBER_NEG:
+       {
+               switch (token_type) {
+               case G_TOKEN_INT:
+               {
+                       /* Negative integer */
+                       uint64_t int_val = state->scanner->value.v_int64;
+
+                       if (int_val > (1ULL << 63) - 1) {
+                               g_string_append_printf(state->ini_error,
+                                       "Integer value -%" PRIu64 " is outside the range of a 64-bit signed integer\n",
+                                       int_val);
+                               goto error;
+                       }
+
+                       value = bt_value_integer_create_init(
+                               -((int64_t) int_val));
+                       break;
+               }
+               case G_TOKEN_FLOAT:
+                       /* Negative floating point number */
+                       value = bt_value_float_create_init(
+                               -state->scanner->value.v_float);
+                       break;
+               default:
+                       /* Unset value variable will trigger the error */
+                       break;
+               }
+
+               if (!value) {
+                       ini_append_error_expecting(state,
+                               state->scanner, "value");
+                       goto error;
+               }
+
+               state->expecting = INI_EXPECT_COMMA;
+               goto success;
+       }
+       case INI_EXPECT_COMMA:
+               if (token_type != G_TOKEN_CHAR) {
+                       ini_append_error_expecting(state,
+                               state->scanner, "','");
+                       goto error;
+               }
+
+               if (state->scanner->value.v_char != ',') {
+                       ini_append_error_expecting(state,
+                               state->scanner, "','");
+                       goto error;
+               }
+
+               state->expecting = INI_EXPECT_MAP_KEY;
+               goto success;
+       default:
+               assert(false);
+       }
+
+error:
+       ret = -1;
+       goto end;
+
+success:
+       if (value) {
+               if (bt_value_map_insert(state->params,
+                               state->last_map_key, value)) {
+                       /* Only override return value on error */
+                       ret = -1;
+               }
+       }
+
+end:
+       BT_PUT(value);
+       return ret;
+}
+
+/*
+ * Converts an INI-style argument to an equivalent map value object.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *bt_value_from_ini(const char *arg, GString *ini_error)
+{
+       /* Lexical scanner configuration */
+       GScannerConfig scanner_config = {
+               /* Skip whitespaces */
+               .cset_skip_characters = " \t\n",
+
+               /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
+               .cset_identifier_first =
+                       G_CSET_a_2_z
+                       "_"
+                       G_CSET_A_2_Z,
+               .cset_identifier_nth =
+                       G_CSET_a_2_z
+                       "_0123456789-.:"
+                       G_CSET_A_2_Z,
+
+               /* "hello" and "Hello" two different keys */
+               .case_sensitive = TRUE,
+
+               /* No comments */
+               .cpair_comment_single = NULL,
+               .skip_comment_multi = TRUE,
+               .skip_comment_single = TRUE,
+               .scan_comment_multi = FALSE,
+
+               /*
+                * Do scan identifiers, including 1-char identifiers,
+                * but NULL is a normal identifier.
+                */
+               .scan_identifier = TRUE,
+               .scan_identifier_1char = TRUE,
+               .scan_identifier_NULL = FALSE,
+
+               /*
+                * No specific symbols: null and boolean "symbols" are
+                * scanned as plain identifiers.
+                */
+               .scan_symbols = FALSE,
+               .symbol_2_token = FALSE,
+               .scope_0_fallback = FALSE,
+
+               /*
+                * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
+                * integers prefixed with "$".
+                */
+               .scan_binary = TRUE,
+               .scan_octal = TRUE,
+               .scan_float = TRUE,
+               .scan_hex = TRUE,
+               .scan_hex_dollar = FALSE,
+
+               /* Convert scanned numbers to integer tokens */
+               .numbers_2_int = TRUE,
+
+               /* Support both integers and floating-point numbers */
+               .int_2_float = FALSE,
+
+               /* Scan integers as 64-bit signed integers */
+               .store_int64 = TRUE,
+
+               /* Only scan double-quoted strings */
+               .scan_string_sq = FALSE,
+               .scan_string_dq = TRUE,
+
+               /* Do not converter identifiers to string tokens */
+               .identifier_2_string = FALSE,
+
+               /* Scan characters as G_TOKEN_CHAR token */
+               .char_2_token = FALSE,
+       };
+       struct ini_parsing_state state = {
+               .scanner = NULL,
+               .params = NULL,
+               .expecting = INI_EXPECT_MAP_KEY,
+               .arg = arg,
+               .ini_error = ini_error,
+       };
+
+       state.params = bt_value_map_create();
+       if (!state.params) {
+               goto error;
+       }
+
+       state.scanner = g_scanner_new(&scanner_config);
+       if (!state.scanner) {
+               goto error;
+       }
+
+       /* Let the scan begin */
+       g_scanner_input_text(state.scanner, arg, strlen(arg));
+
+       while (true) {
+               int ret = ini_handle_state(&state);
+
+               if (ret < 0) {
+                       /* Error */
+                       goto error;
+               } else if (ret > 0) {
+                       /* Done */
+                       break;
+               }
+       }
+
+       goto end;
+
+error:
+       BT_PUT(state.params);
+
+end:
+       if (state.scanner) {
+               g_scanner_destroy(state.scanner);
+       }
+
+       free(state.last_map_key);
+       return state.params;
+}
+
+/*
+ * Returns the parameters map value object from a command-line
+ * source/sink option's argument. arg is the full argument, including
+ * the plugin/component names, the optional colon, and the optional
+ * parameters.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *bt_value_from_arg(const char *arg)
+{
+       struct bt_value *params = NULL;
+       const char *colon;
+       const char *params_string;
+       GString *ini_error = NULL;
+
+       /* Isolate parameters */
+       colon = strchr(arg, ':');
+       if (!colon) {
+               /* No colon: empty parameters */
+               params = bt_value_map_create();
+               goto end;
+       }
+
+       params_string = colon + 1;
+       ini_error = g_string_new(NULL);
+       if (!ini_error) {
+               print_err_oom();
+               goto end;
+       }
+
+       /* Try INI-style parsing */
+       params = bt_value_from_ini(params_string, ini_error);
+       if (!params) {
+               printf_err("%s", ini_error->str);
+               goto end;
+       }
+
+end:
+       if (ini_error) {
+               g_string_free(ini_error, TRUE);
+       }
+       return params;
+}
+
+/*
+ * Returns the plugin and component names from a command-line
+ * source/sink option's argument. arg is the full argument, including
+ * the plugin/component names, the optional colon, and the optional
+ * parameters.
+ *
+ * On success, both *plugin and *component are not NULL. *plugin
+ * and *component are owned by the caller.
+ */
+static
+void plugin_component_names_from_arg(const char *arg, char **plugin,
+               char **component)
+{
+       const char *dot;
+       const char *colon;
+       size_t plugin_len;
+       size_t component_len;
+
+       *plugin = NULL;
+       *component = NULL;
+
+       dot = strchr(arg, '.');
+       if (!dot || dot == arg) {
+               goto end;
+       }
+
+       colon = strchr(dot, ':');
+       if (colon == dot) {
+               goto end;
+       }
+       if (!colon) {
+               colon = arg + strlen(arg);
+       }
+
+       plugin_len = dot - arg;
+       component_len = colon - dot - 1;
+       if (plugin_len == 0 || component_len == 0) {
+               goto end;
+       }
+
+       *plugin = malloc(plugin_len + 1);
+       if (!*plugin) {
+               print_err_oom();
+               goto end;
+       }
+
+       (*plugin)[plugin_len] = '\0';
+       memcpy(*plugin, arg, plugin_len);
+       *component = malloc(component_len + 1);
+       if (!*component) {
+               print_err_oom();
+               goto end;
+       }
+
+       (*component)[component_len] = '\0';
+       memcpy(*component, dot + 1, component_len);
+
+end:
+       return;
+}
+
+/*
+ * Prints the Babeltrace version.
+ */
+static
+void print_version(void)
+{
+       puts("Babeltrace " VERSION);
+}
+
+/*
+ * Prints the legacy, Babeltrace 1.x command usage. Those options are
+ * still compatible in Babeltrace 2.x, but it is recommended to use
+ * the more generic plugin/component parameters instead of those
+ * hard-coded option names.
+ */
+static
+void print_legacy_usage(FILE *fp)
+{
+       fprintf(fp, "Usage: babeltrace [OPTIONS] INPUT...\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "The following options are compatible with the Babeltrace 1.x options:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "      --help-legacy            Show this help\n");
+       fprintf(fp, "  -V, --version                Show version\n");
+       fprintf(fp, "      --clock-force-correlate  Assume that clocks are inherently correlated\n");
+       fprintf(fp, "                               across traces\n");
+       fprintf(fp, "  -d, --debug                  Enable debug mode\n");
+       fprintf(fp, "  -i, --input-format=FORMAT    Input trace format (default: ctf)\n");
+       fprintf(fp, "  -l, --list                   List available formats\n");
+       fprintf(fp, "  -o, --output-format=FORMAT   Output trace format (default: text)\n");
+       fprintf(fp, "  -v, --verbose                Enable verbose output\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "  Available input formats:  ctf, lttng-live, ctf-metadata\n");
+       fprintf(fp, "  Available output formats: text, dummy\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "Input formats specific options:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "  INPUT...                     Input trace file(s), directory(ies), or URLs\n");
+       fprintf(fp, "      --clock-offset=SEC       Set clock offset to SEC seconds\n");
+       fprintf(fp, "      --clock-offset-ns=NS     Set clock offset to NS nanoseconds\n");
+       fprintf(fp, "      --stream-intersection    Only process events when all streams are active\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "text output format specific options:\n");
+       fprintf(fp, "  \n");
+       fprintf(fp, "      --clock-cycles           Print timestamps in clock cycles\n");
+       fprintf(fp, "      --clock-date             Print timestamp dates\n");
+       fprintf(fp, "      --clock-gmt              Print timestamps in GMT time zone\n");
+       fprintf(fp, "                               (default: local time zone)\n");
+       fprintf(fp, "      --clock-seconds          Print the timestamps as [SEC.NS]\n");
+       fprintf(fp, "                               (default format: [HH:MM:SS.NS])\n");
+       fprintf(fp, "      --debug-info-dir=DIR     Search for debug info in directory DIR\n");
+       fprintf(fp, "                               (default: \"/usr/lib/debug\")\n");
+       fprintf(fp, "      --debug-info-full-path   Show full debug info source and binary paths\n");
+       fprintf(fp, "      --debug-info-target-prefix=DIR  Use directory DIR as a prefix when looking\n");
+       fprintf(fp, "                                      up executables during debug info analysis\n");
+       fprintf(fp, "                               (default: \"/usr/lib/debug\")\n");
+       fprintf(fp, "  -f, --fields=NAME[,NAME]...  Print additional fields:\n");
+       fprintf(fp, "                                 all, trace, trace:hostname, trace:domain,\n");
+       fprintf(fp, "                                 trace:procname, trace:vpid, loglevel, emf,\n");
+       fprintf(fp, "                                 callsite\n");
+       fprintf(fp, "                                 (default: trace:hostname, trace:procname,\n");
+       fprintf(fp, "                                           trace:vpid)\n");
+       fprintf(fp, "  -n, --names=NAME[,NAME]...   Print field names:\n");
+       fprintf(fp, "                                 payload (or arg or args)\n");
+       fprintf(fp, "                                 none, all, scope, header, context (or ctx)\n");
+       fprintf(fp, "                                 (default: payload, context)\n");
+       fprintf(fp, "      --no-delta               Do not print time delta between consecutive\n");
+       fprintf(fp, "                               events\n");
+       fprintf(fp, "  -w, --output=PATH            Write output to PATH (default: standard output)\n");
+}
+
+/*
+ * Prints the Babeltrace 2.x usage.
+ */
+static
+void print_usage(FILE *fp)
+{
+       fprintf(fp, "Usage: babeltrace [OPTIONS]\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "  -h  --help                        Show this help\n");
+       fprintf(fp, "      --help-legacy                 Show Babeltrace 1.x legacy options\n");
+       fprintf(fp, "  -d, --debug                       Enable debug mode\n");
+       fprintf(fp, "  -l, --list                        List available plugins and their components\n");
+       fprintf(fp, "  -p, --plugin-path=PATH[:PATH]...  Set paths from which dynamic plugins can be\n");
+       fprintf(fp, "                                    loaded to PATH\n");
+       fprintf(fp, "  -i, --source=SOURCE               Add source plugin/component SOURCE and its\n");
+       fprintf(fp, "                                    parameters to the active sources (may be\n");
+       fprintf(fp, "                                    repeated; see the exact format below)\n");
+       fprintf(fp, "  -o, --sink=SINK                   Add sink plugin/component SINK and its\n");
+       fprintf(fp, "                                    parameters to the active sinks (may be\n");
+       fprintf(fp, "                                    repeated; see the exact format below)\n");
+       fprintf(fp, "  -v, --verbose                     Enable verbose output\n");
+       fprintf(fp, "  -V, --version                     Show version\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "SOURCE/SINK argument format:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "  One of:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "    PLUGIN.COMPONENT\n");
+       fprintf(fp, "      Load component COMPONENT from plugin PLUGIN with its default parameters.\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "    PLUGIN.COMPONENT:PARAM=VALUE[,PARAM=VALUE]...\n");
+       fprintf(fp, "      Load component COMPONENT from plugin PLUGIN with the specified parameters.\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "      The parameter string is a comma-separated list of PARAM=VALUE assignments,\n");
+       fprintf(fp, "      where PARAM is the parameter name (C identifier plus [:.-] characters), and\n");
+       fprintf(fp, "      VALUE can be one of:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "        * \"null\", \"nul\", \"NULL\": null value (no double quotes)\n");
+       fprintf(fp, "        * \"true\", \"TRUE\", \"yes\", \"YES\": true boolean value (no double quotes)\n");
+       fprintf(fp, "        * \"false\", \"FALSE\", \"no\", \"NO\": false boolean value (no double quotes)\n");
+       fprintf(fp, "        * Binary (\"0b\" prefix), octal (\"0\" prefix), decimal, or\n");
+       fprintf(fp, "          hexadecimal (\"0x\" prefix) signed 64-bit integer\n");
+       fprintf(fp, "        * Double precision floating point number (scientific notation is accepted)\n");
+       fprintf(fp, "        * Unquoted string with no special characters, and not matching any of\n");
+       fprintf(fp, "          the null and boolean value symbols above\n");
+       fprintf(fp, "        * Double-quoted string (accepts escape characters)\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "      Example:\n");
+       fprintf(fp, "\n");
+       fprintf(fp, "          plugin.comp:many=null, fresh=yes, condition=false, squirrel=-782329,\n");
+       fprintf(fp, "                      observe=3.14, simple=beef, needs-quotes=\"some string\",\n");
+       fprintf(fp, "                      escape.chars-are:allowed=\"this is a \\\" double quote\"\n");
+}
+
+/*
+ * Destroys a component configuration.
+ */
+static
+void bt_config_component_destroy(struct bt_object *obj)
+{
+       struct bt_config_component *bt_config_component =
+               container_of(obj, struct bt_config_component, base);
+
+       if (!obj) {
+               goto end;
+       }
+
+       if (bt_config_component->plugin_name) {
+               g_string_free(bt_config_component->plugin_name, TRUE);
+       }
+
+       if (bt_config_component->component_name) {
+               g_string_free(bt_config_component->component_name, TRUE);
+       }
+
+       BT_PUT(bt_config_component->params);
+       g_free(bt_config_component);
+
+end:
+       return;
+}
+
+/*
+ * Creates a component configuration using the given plugin name,
+ * component name, and parameters. plugin_name and component_name
+ * are copied (belong to the return value), and a reference to
+ * params is acquired.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_config_component *bt_config_component_create(const char *plugin_name,
+               const char *component_name, struct bt_value *params)
+{
+       struct bt_config_component *cfg_component = NULL;
+
+       cfg_component = g_new0(struct bt_config_component, 1);
+       if (!cfg_component) {
+               print_err_oom();
+               goto error;
+       }
+
+       bt_object_init(cfg_component, bt_config_component_destroy);
+       cfg_component->plugin_name = g_string_new(plugin_name);
+       if (!cfg_component->plugin_name) {
+               print_err_oom();
+               goto error;
+       }
+
+       cfg_component->component_name = g_string_new(component_name);
+       if (!cfg_component->component_name) {
+               print_err_oom();
+               goto error;
+       }
+
+       cfg_component->params = bt_get(params);
+       goto end;
+
+error:
+       BT_PUT(cfg_component);
+
+end:
+       return cfg_component;
+}
+
+/*
+ * Creates a component configuration from a command-line source/sink
+ * option's argument. arg is the full argument, including
+ * the plugin/component names, the optional colon, and the optional
+ * parameters.
+ */
+static
+struct bt_config_component *bt_config_component_from_arg(const char *arg)
+{
+       struct bt_config_component *bt_config_component = NULL;
+       char *plugin_name;
+       char *component_name;
+       struct bt_value *params = NULL;
+
+       plugin_component_names_from_arg(arg, &plugin_name, &component_name);
+       if (!plugin_name || !component_name) {
+               printf_err("Cannot get plugin or component name\n");
+               goto error;
+       }
+
+       params = bt_value_from_arg(arg);
+       if (!params) {
+               printf_err("Cannot parse parameters\n");
+               goto error;
+       }
+
+       bt_config_component = bt_config_component_create(plugin_name,
+               component_name, params);
+       if (!bt_config_component) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       BT_PUT(bt_config_component);
+
+end:
+       free(plugin_name);
+       free(component_name);
+       BT_PUT(params);
+       return bt_config_component;
+}
+
+/*
+ * Destroys a configuration.
+ */
+static
+void bt_config_destroy(struct bt_object *obj)
+{
+       struct bt_config *bt_config =
+               container_of(obj, struct bt_config, base);
+
+       if (!obj) {
+               goto end;
+       }
+
+       if (bt_config->sources) {
+               g_ptr_array_free(bt_config->sources, TRUE);
+       }
+
+       if (bt_config->sinks) {
+               g_ptr_array_free(bt_config->sinks, TRUE);
+       }
+
+       BT_PUT(bt_config->plugin_paths);
+       g_free(bt_config);
+
+end:
+       return;
+}
+
+/*
+ * Extracts the various paths from the string arg, delimited by ':',
+ * and converts them to an array value object.
+ *
+ * Returned array value object is empty if arg is empty.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *plugin_paths_from_arg(const char *arg)
+{
+       struct bt_value *plugin_paths;
+       const char *at = arg;
+       const char *end = arg + strlen(arg);
+
+       plugin_paths = bt_value_array_create();
+       if (!plugin_paths) {
+               print_err_oom();
+               goto error;
+       }
+
+       while (at < end) {
+               int ret;
+               GString *path;
+               const char *next_colon;
+
+               next_colon = strchr(at, ':');
+               if (next_colon == at) {
+                       /*
+                        * Empty path: try next character (supported
+                        * to conform to the typical parsing of $PATH).
+                        */
+                       at++;
+                       continue;
+               } else if (!next_colon) {
+                       /* No more colon: use the remaining */
+                       next_colon = arg + strlen(arg);
+               }
+
+               path = g_string_new(NULL);
+               if (!path) {
+                       print_err_oom();
+                       goto error;
+               }
+
+               g_string_append_len(path, at, next_colon - at);
+               at = next_colon + 1;
+               ret = bt_value_array_append_string(plugin_paths, path->str);
+               g_string_free(path, TRUE);
+               if (ret) {
+                       print_err_oom();
+                       goto error;
+               }
+       }
+
+       goto end;
+
+error:
+       BT_PUT(plugin_paths);
+
+end:
+       return plugin_paths;
+}
+
+/*
+ * Creates a simple lexical scanner for parsing comma-delimited names
+ * and fields.
+ *
+ * Return value is owned by the caller.
+ */
+static
+GScanner *create_csv_identifiers_scanner(void)
+{
+       GScannerConfig scanner_config = {
+               .cset_skip_characters = " \t\n",
+               .cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "_",
+               .cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z ":_-",
+               .case_sensitive = TRUE,
+               .cpair_comment_single = NULL,
+               .skip_comment_multi = TRUE,
+               .skip_comment_single = TRUE,
+               .scan_comment_multi = FALSE,
+               .scan_identifier = TRUE,
+               .scan_identifier_1char = TRUE,
+               .scan_identifier_NULL = FALSE,
+               .scan_symbols = FALSE,
+               .symbol_2_token = FALSE,
+               .scope_0_fallback = FALSE,
+               .scan_binary = FALSE,
+               .scan_octal = FALSE,
+               .scan_float = FALSE,
+               .scan_hex = FALSE,
+               .scan_hex_dollar = FALSE,
+               .numbers_2_int = FALSE,
+               .int_2_float = FALSE,
+               .store_int64 = FALSE,
+               .scan_string_sq = FALSE,
+               .scan_string_dq = FALSE,
+               .identifier_2_string = FALSE,
+               .char_2_token = TRUE,
+       };
+
+       return g_scanner_new(&scanner_config);
+}
+
+/*
+ * Converts a comma-delimited list of known names (--names option) to
+ * an array value object containing those names as string value objects.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *names_from_arg(const char *arg)
+{
+       GScanner *scanner = NULL;
+       struct bt_value *names = NULL;
+
+       names = bt_value_array_create();
+       if (!names) {
+               print_err_oom();
+               goto error;
+       }
+
+       scanner = create_csv_identifiers_scanner();
+       if (!scanner) {
+               print_err_oom();
+               goto error;
+       }
+
+       g_scanner_input_text(scanner, arg, strlen(arg));
+
+       while (true) {
+               GTokenType token_type = g_scanner_get_next_token(scanner);
+
+               switch (token_type) {
+               case G_TOKEN_IDENTIFIER:
+               {
+                       const char *identifier = scanner->value.v_identifier;
+
+                       if (!strcmp(identifier, "payload") ||
+                                       !strcmp(identifier, "args") ||
+                                       !strcmp(identifier, "arg")) {
+                               if (bt_value_array_append_string(names,
+                                               "payload")) {
+                                       goto error;
+                               }
+                       } else if (!strcmp(identifier, "context") ||
+                                       !strcmp(identifier, "ctx")) {
+                               if (bt_value_array_append_string(names,
+                                               "context")) {
+                                       goto error;
+                               }
+                       } else if (!strcmp(identifier, "scope") ||
+                                       !strcmp(identifier, "header")) {
+                               if (bt_value_array_append_string(names,
+                                               identifier)) {
+                                       goto error;
+                               }
+                       } else if (!strcmp(identifier, "all") ||
+                                       !strcmp(identifier, "none")) {
+                               /*
+                                * "all" and "none" override all the
+                                * specific names.
+                                */
+                               BT_PUT(names);
+                               names = bt_value_array_create();
+                               if (!names) {
+                                       print_err_oom();
+                                       goto error;
+                               }
+
+                               if (bt_value_array_append_string(names,
+                                               identifier)) {
+                                       goto error;
+                               }
+                               goto end;
+                       } else {
+                               printf_err("Unknown field name: \"%s\"\n",
+                                       identifier);
+                               goto error;
+                       }
+                       break;
+               }
+               case G_TOKEN_COMMA:
+                       continue;
+               case G_TOKEN_EOF:
+                       goto end;
+               default:
+                       goto error;
+               }
+       }
+
+       goto end;
+
+error:
+       BT_PUT(names);
+
+end:
+       if (scanner) {
+               g_scanner_destroy(scanner);
+       }
+       return names;
+}
+
+
+/*
+ * Converts a comma-delimited list of known fields (--fields option) to
+ * an array value object containing those fields as string
+ * value objects.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *fields_from_arg(const char *arg)
+{
+       GScanner *scanner = NULL;
+       struct bt_value *fields;
+
+       fields = bt_value_array_create();
+       if (!fields) {
+               print_err_oom();
+               goto error;
+       }
+
+       scanner = create_csv_identifiers_scanner();
+       if (!scanner) {
+               print_err_oom();
+               goto error;
+       }
+
+       g_scanner_input_text(scanner, arg, strlen(arg));
+
+       while (true) {
+               GTokenType token_type = g_scanner_get_next_token(scanner);
+
+               switch (token_type) {
+               case G_TOKEN_IDENTIFIER:
+               {
+                       const char *identifier = scanner->value.v_identifier;
+
+                       if (!strcmp(identifier, "trace") ||
+                                       !strcmp(identifier, "trace:hostname") ||
+                                       !strcmp(identifier, "trace:domain") ||
+                                       !strcmp(identifier, "trace:procname") ||
+                                       !strcmp(identifier, "trace:vpid") ||
+                                       !strcmp(identifier, "loglevel") ||
+                                       !strcmp(identifier, "emf") ||
+                                       !strcmp(identifier, "callsite")) {
+                               if (bt_value_array_append_string(fields,
+                                               identifier)) {
+                                       goto error;
+                               }
+                       } else if (!strcmp(identifier, "all")) {
+                               /* "all" override all the specific fields */
+                               BT_PUT(fields);
+                               fields = bt_value_array_create();
+                               if (!fields) {
+                                       print_err_oom();
+                                       goto error;
+                               }
+
+                               if (bt_value_array_append_string(fields,
+                                               identifier)) {
+                                       goto error;
+                               }
+                               goto end;
+                       } else {
+                               printf_err("Unknown field name: \"%s\"\n",
+                                       identifier);
+                               goto error;
+                       }
+                       break;
+               }
+               case G_TOKEN_COMMA:
+                       continue;
+               case G_TOKEN_EOF:
+                       goto end;
+               default:
+                       goto error;
+               }
+       }
+
+       goto end;
+
+error:
+       BT_PUT(fields);
+
+end:
+       if (scanner) {
+               g_scanner_destroy(scanner);
+       }
+       return fields;
+}
+
+/*
+ * Inserts the equivalent "prefix-name" true boolean value objects into
+ * map_obj where the names are in array_obj.
+ */
+static
+int insert_flat_names_fields_from_array(struct bt_value *map_obj,
+               struct bt_value *array_obj, const char *prefix)
+{
+       int ret = 0;
+       int i;
+       GString *tmpstr = NULL;
+
+       /*
+        * array_obj may be NULL if no CLI options were specified to
+        * trigger its creation.
+        */
+       if (!array_obj) {
+               goto end;
+       }
+
+       tmpstr = g_string_new(NULL);
+       if (!tmpstr) {
+               print_err_oom();
+               ret = -1;
+               goto end;
+       }
+
+       for (i = 0; i < bt_value_array_size(array_obj); i++) {
+               struct bt_value *str_obj = bt_value_array_get(array_obj, i);
+               const char *suffix;
+
+               if (!str_obj) {
+                       printf_err("Unexpected error\n");
+                       ret = -1;
+                       goto end;
+               }
+
+               ret = bt_value_string_get(str_obj, &suffix);
+               BT_PUT(str_obj);
+               if (ret) {
+                       printf_err("Unexpected error\n");
+                       goto end;
+               }
+
+               g_string_assign(tmpstr, prefix);
+               g_string_append(tmpstr, "-");
+               g_string_append(tmpstr, suffix);
+               ret = bt_value_map_insert_bool(map_obj, tmpstr->str, true);
+               if (ret) {
+                       print_err_oom();
+                       goto end;
+               }
+       }
+
+end:
+       if (tmpstr) {
+               g_string_free(tmpstr, TRUE);
+       }
+
+       return ret;
+}
+
+/*
+ * Inserts a string (if exists and not empty) or null to a map value
+ * object.
+ */
+static
+enum bt_value_status map_insert_string_or_null(struct bt_value *map,
+               const char *key, GString *string)
+{
+       enum bt_value_status ret;
+
+       if (string && string->len > 0) {
+               ret = bt_value_map_insert_string(map, key, string->str);
+       } else {
+               ret = bt_value_map_insert(map, key, bt_value_null);
+       }
+       return ret;
+}
+
+/*
+ * Returns the parameters (map value object) corresponding to the
+ * legacy text format options.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *params_from_text_legacy_opts(
+               struct text_legacy_opts *text_legacy_opts)
+{
+       struct bt_value *params;
+
+       params = bt_value_map_create();
+       if (!params) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (map_insert_string_or_null(params, "output-path",
+                       text_legacy_opts->output)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (map_insert_string_or_null(params, "debug-info-dir",
+                       text_legacy_opts->dbg_info_dir)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (map_insert_string_or_null(params, "debug-info-target-prefix",
+                       text_legacy_opts->dbg_info_target_prefix)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "debug-info-full-path",
+                       text_legacy_opts->dbg_info_full_path)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "no-delta",
+                       text_legacy_opts->no_delta)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "clock-cycles",
+                       text_legacy_opts->clock_cycles)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "clock-seconds",
+                       text_legacy_opts->clock_seconds)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "clock-date",
+                       text_legacy_opts->clock_date)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "clock-gmt",
+                       text_legacy_opts->clock_gmt)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (insert_flat_names_fields_from_array(params,
+                       text_legacy_opts->names, "name")) {
+               goto error;
+       }
+
+       if (insert_flat_names_fields_from_array(params,
+                       text_legacy_opts->fields, "field")) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       BT_PUT(params);
+
+end:
+       return params;
+}
+
+static
+int append_sinks_from_legacy_opts(GPtrArray *sinks,
+               enum legacy_output_format legacy_output_format,
+               struct text_legacy_opts *text_legacy_opts)
+{
+       int ret = 0;
+       struct bt_value *params = NULL;
+       const char *plugin_name;
+       const char *component_name;
+       struct bt_config_component *bt_config_component = NULL;
+
+       switch (legacy_output_format) {
+       case LEGACY_OUTPUT_FORMAT_TEXT:
+               plugin_name = "text";
+               component_name = "text";
+               break;
+       case LEGACY_OUTPUT_FORMAT_CTF_METADATA:
+               plugin_name = "ctf";
+               component_name = "metadata-text";
+               break;
+       case LEGACY_OUTPUT_FORMAT_DUMMY:
+               plugin_name = "dummy";
+               component_name = "dummy";
+               break;
+       default:
+               assert(false);
+               break;
+       }
+
+       if (legacy_output_format == LEGACY_OUTPUT_FORMAT_TEXT) {
+               /* Legacy "text" output format has parameters */
+               params = params_from_text_legacy_opts(text_legacy_opts);
+               if (!params) {
+                       goto error;
+               }
+       } else {
+               /*
+                * Legacy "dummy" and "ctf-metadata" output formats do
+                * not have parameters.
+                */
+               params = bt_value_map_create();
+               if (!params) {
+                       print_err_oom();
+                       goto error;
+               }
+       }
+
+       /* Create a component configuration */
+       bt_config_component = bt_config_component_create(plugin_name,
+               component_name, params);
+       if (!bt_config_component) {
+               goto error;
+       }
+
+       /* Move created component configuration to the array */
+       g_ptr_array_add(sinks, bt_config_component);
+
+       goto end;
+
+error:
+       ret = -1;
+
+end:
+       BT_PUT(params);
+
+       return ret;
+}
+
+/*
+ * Returns the parameters (map value object) corresponding to the
+ * given legacy CTF format options.
+ *
+ * Return value is owned by the caller.
+ */
+static
+struct bt_value *params_from_ctf_legacy_opts(
+               struct ctf_legacy_opts *ctf_legacy_opts)
+{
+       struct bt_value *params;
+
+       params = bt_value_map_create();
+       if (!params) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_integer(params, "offset-s",
+                       ctf_legacy_opts->offset_s.value)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_integer(params, "offset-ns",
+                       ctf_legacy_opts->offset_ns.value)) {
+               print_err_oom();
+               goto error;
+       }
+
+       if (bt_value_map_insert_bool(params, "stream-intersection",
+                       ctf_legacy_opts->stream_intersection)) {
+               print_err_oom();
+               goto error;
+       }
+
+       goto end;
+
+error:
+       BT_PUT(params);
+
+end:
+       return params;
+}
+
+static
+int append_sources_from_legacy_opts(GPtrArray *sources,
+               enum legacy_input_format legacy_input_format,
+               struct ctf_legacy_opts *ctf_legacy_opts,
+               struct bt_value *legacy_input_paths)
+{
+       int ret = 0;
+       int i;
+       struct bt_value *base_params;
+       struct bt_value *params = NULL;
+       struct bt_value *input_path = NULL;
+       struct bt_value *input_path_copy = NULL;
+       const char *input_key;
+       const char *component_name;
+
+       switch (legacy_input_format) {
+       case LEGACY_INPUT_FORMAT_CTF:
+               input_key = "path";
+               component_name = "fs";
+               break;
+       case LEGACY_INPUT_FORMAT_LTTNG_LIVE:
+               input_key = "url";
+               component_name = "lttng-live";
+               break;
+       default:
+               assert(false);
+               break;
+       }
+
+       base_params = params_from_ctf_legacy_opts(ctf_legacy_opts);
+       if (!base_params) {
+               goto error;
+       }
+
+       for (i = 0; i < bt_value_array_size(legacy_input_paths); i++) {
+               struct bt_config_component *bt_config_component = NULL;
+
+               /* Copy base parameters as current parameters */
+               params = bt_value_copy(base_params);
+               if (!params) {
+                       goto error;
+               }
+
+               /* Get current input path string value object */
+               input_path = bt_value_array_get(legacy_input_paths, i);
+               if (!input_path) {
+                       goto error;
+               }
+
+               /* Copy current input path value object */
+               input_path_copy = bt_value_copy(input_path);
+               if (!input_path_copy) {
+                       goto error;
+               }
+
+               /* Insert input path value object into current parameters */
+               ret = bt_value_map_insert(params, input_key, input_path_copy);
+               if (ret) {
+                       goto error;
+               }
+
+               /* Create a component configuration */
+               bt_config_component = bt_config_component_create("ctf",
+                       component_name, params);
+               if (!bt_config_component) {
+                       goto error;
+               }
+
+               /* Move created component configuration to the array */
+               g_ptr_array_add(sources, bt_config_component);
+
+               /* Put current stuff */
+               BT_PUT(input_path);
+               BT_PUT(input_path_copy);
+               BT_PUT(params);
+       }
+
+       goto end;
+
+error:
+       ret = -1;
+
+end:
+       BT_PUT(base_params);
+       BT_PUT(params);
+       BT_PUT(input_path);
+       BT_PUT(input_path_copy);
+       return ret;
+}
+
+/*
+ * Escapes a string for the shell. The string is escaped knowing that
+ * it's a parameter string value (double-quoted), and that it will be
+ * entered between single quotes in the shell.
+ *
+ * Return value is owned by the caller.
+ */
+static
+char *str_shell_escape(const char *input)
+{
+       char *ret = NULL;
+       const char *at = input;
+       GString *str = g_string_new(NULL);
+
+       if (!str) {
+               goto end;
+       }
+
+       while (*at != '\0') {
+               switch (*at) {
+               case '\\':
+                       g_string_append(str, "\\\\");
+                       break;
+               case '"':
+                       g_string_append(str, "\\\"");
+                       break;
+               case '\'':
+                       g_string_append(str, "'\"'\"'");
+                       break;
+               case '\n':
+                       g_string_append(str, "\\n");
+                       break;
+               case '\t':
+                       g_string_append(str, "\\t");
+                       break;
+               default:
+                       g_string_append_c(str, *at);
+                       break;
+               }
+
+               at++;
+       }
+
+end:
+       if (str) {
+               ret = str->str;
+               g_string_free(str, FALSE);
+       }
+
+       return ret;
+}
+
+static
+int append_prefixed_flag_params(GString *str, struct bt_value *flags,
+               const char *prefix)
+{
+       int ret = 0;
+       int i;
+
+       if (!flags) {
+               goto end;
+       }
+
+       for (i = 0; i < bt_value_array_size(flags); i++) {
+               struct bt_value *value = bt_value_array_get(flags, i);
+               const char *flag;
+
+               if (!value) {
+                       ret = -1;
+                       goto end;
+               }
+
+               if (bt_value_string_get(value, &flag)) {
+                       BT_PUT(value);
+                       ret = -1;
+                       goto end;
+               }
+
+               g_string_append_printf(str, ",%s-%s=true", prefix, flag);
+               BT_PUT(value);
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * Appends a boolean parameter string.
+ */
+static
+void g_string_append_bool_param(GString *str, const char *name, bool value)
+{
+       g_string_append_printf(str, ",%s=%s", name, value ? "true" : "false");
+}
+
+/*
+ * Appends a path parameter string, or null if it's empty.
+ */
+static
+int g_string_append_string_path_param(GString *str, const char *name,
+               GString *path)
+{
+       int ret = 0;
+
+       if (path->len > 0) {
+               char *escaped_path = str_shell_escape(path->str);
+
+               if (!escaped_path) {
+                       print_err_oom();
+                       goto error;
+               }
+
+               g_string_append_printf(str, "%s=\"%s\"", name, escaped_path);
+               free(escaped_path);
+       } else {
+               g_string_append_printf(str, "%s=null", name);
+       }
+
+       goto end;
+
+error:
+       ret = -1;
+
+end:
+       return ret;
+}
+
+/*
+ * Prints the non-legacy sink options equivalent to the specified
+ * legacy output format options.
+ */
+static
+void print_output_legacy_to_sinks(
+               enum legacy_output_format legacy_output_format,
+               struct text_legacy_opts *text_legacy_opts)
+{
+       const char *input_format;
+       GString *str = NULL;
+
+       str = g_string_new("    ");
+       if (!str) {
+               print_err_oom();
+               goto end;
+       }
+
+       switch (legacy_output_format) {
+       case LEGACY_OUTPUT_FORMAT_TEXT:
+               input_format = "text";
+               break;
+       case LEGACY_OUTPUT_FORMAT_CTF_METADATA:
+               input_format = "ctf-metadata";
+               break;
+       case LEGACY_OUTPUT_FORMAT_DUMMY:
+               input_format = "dummy";
+               break;
+       default:
+               assert(false);
+       }
+
+       printf_err("Both \"%s\" legacy output format and non-legacy sink(s) specified.\n\n",
+               input_format);
+       printf_err("Specify the following non-legacy sink instead of the legacy \"%s\"\noutput format options:\n\n",
+               input_format);
+       g_string_append(str, "-o ");
+
+       switch (legacy_output_format) {
+       case LEGACY_OUTPUT_FORMAT_TEXT:
+               g_string_append(str, "text.text");
+               break;
+       case LEGACY_OUTPUT_FORMAT_CTF_METADATA:
+               g_string_append(str, "ctf.metadata-text");
+               break;
+       case LEGACY_OUTPUT_FORMAT_DUMMY:
+               g_string_append(str, "dummy.dummy");
+               break;
+       default:
+               assert(false);
+       }
+
+       if (legacy_output_format == LEGACY_OUTPUT_FORMAT_TEXT &&
+                       text_legacy_opts_is_any_set(text_legacy_opts)) {
+               int ret;
+
+               g_string_append(str, ":'");
+
+               if (g_string_append_string_path_param(str, "output-path",
+                               text_legacy_opts->output)) {
+                       goto end;
+               }
+
+               g_string_append(str, ",");
+
+               if (g_string_append_string_path_param(str, "debug-info-dir",
+                               text_legacy_opts->dbg_info_dir)) {
+                       goto end;
+               }
+
+               g_string_append(str, ",");
+
+               if (g_string_append_string_path_param(str,
+                               "debug-info-target-prefix",
+                               text_legacy_opts->dbg_info_target_prefix)) {
+                       goto end;
+               }
+
+               g_string_append_bool_param(str, "no-delta",
+                       text_legacy_opts->no_delta);
+               g_string_append_bool_param(str, "clock-cycles",
+                       text_legacy_opts->clock_cycles);
+               g_string_append_bool_param(str, "clock-seconds",
+                       text_legacy_opts->clock_seconds);
+               g_string_append_bool_param(str, "clock-date",
+                       text_legacy_opts->clock_date);
+               g_string_append_bool_param(str, "clock-gmt",
+                       text_legacy_opts->clock_gmt);
+               ret = append_prefixed_flag_params(str, text_legacy_opts->names,
+                       "name");
+               if (ret) {
+                       goto end;
+               }
+
+               ret = append_prefixed_flag_params(str, text_legacy_opts->fields,
+                       "field");
+               if (ret) {
+                       goto end;
+               }
+
+               /* Remove last comma and close single quote */
+               g_string_append(str, "'");
+       }
+
+       printf_err("%s\n\n", str->str);
+
+end:
+       if (str) {
+               g_string_free(str, TRUE);
+       }
+       return;
+}
+
+/*
+ * Prints the non-legacy source options equivalent to the specified
+ * legacy input format options.
+ */
+static
+void print_input_legacy_to_sources(enum legacy_input_format legacy_input_format,
+               struct bt_value *legacy_input_paths,
+               struct ctf_legacy_opts *ctf_legacy_opts)
+{
+       const char *input_format;
+       GString *str = NULL;
+       int i;
+
+       str = g_string_new("    ");
+       if (!str) {
+               print_err_oom();
+               goto end;
+       }
+
+       switch (legacy_input_format) {
+       case LEGACY_INPUT_FORMAT_CTF:
+               input_format = "ctf";
+               break;
+       case LEGACY_INPUT_FORMAT_LTTNG_LIVE:
+               input_format = "lttng-live";
+               break;
+       default:
+               assert(false);
+       }
+
+       printf_err("Both \"%s\" legacy input format and non-legacy source(s) specified.\n\n",
+               input_format);
+       printf_err("Specify the following non-legacy source(s) instead of the legacy \"%s\"\ninput format options and positional arguments:\n\n",
+               input_format);
+
+       for (i = 0; i < bt_value_array_size(legacy_input_paths); i++) {
+               struct bt_value *input_value =
+                       bt_value_array_get(legacy_input_paths, i);
+               const char *input = NULL;
+               char *escaped_input;
+               int ret;
+
+               assert(input_value);
+               ret = bt_value_string_get(input_value, &input);
+               BT_PUT(input_value);
+               assert(!ret && input);
+               escaped_input = str_shell_escape(input);
+               if (!escaped_input) {
+                       print_err_oom();
+                       goto end;
+               }
+
+               g_string_append(str, "-i ctf.");
+
+               switch (legacy_input_format) {
+               case LEGACY_INPUT_FORMAT_CTF:
+                       g_string_append(str, "fs:'path=\"");
+                       break;
+               case LEGACY_INPUT_FORMAT_LTTNG_LIVE:
+                       g_string_append(str, "lttng-live:'url=\"");
+                       break;
+               default:
+                       assert(false);
+               }
+
+               g_string_append(str, escaped_input);
+               g_string_append(str, "\"");
+               g_string_append_printf(str, ",offset-s=%" PRId64,
+                       ctf_legacy_opts->offset_s.value);
+               g_string_append_printf(str, ",offset-ns=%" PRId64,
+                       ctf_legacy_opts->offset_ns.value);
+               g_string_append_bool_param(str, "stream-intersection",
+                       ctf_legacy_opts->stream_intersection);
+               g_string_append(str, "' ");
+               g_free(escaped_input);
+       }
+
+       printf_err("%s\n\n", str->str);
+
+end:
+       if (str) {
+               g_string_free(str, TRUE);
+       }
+       return;
+}
+
+/*
+ * Validates a given configuration, with optional legacy input and
+ * output formats options. Prints useful error messages if anything
+ * is wrong.
+ *
+ * Returns true when the configuration is valid.
+ */
+static
+bool validate_cfg(struct bt_config *cfg,
+               enum legacy_input_format *legacy_input_format,
+               enum legacy_output_format *legacy_output_format,
+               struct bt_value *legacy_input_paths,
+               struct ctf_legacy_opts *ctf_legacy_opts,
+               struct text_legacy_opts *text_legacy_opts)
+{
+       bool legacy_input = false;
+       bool legacy_output = false;
+
+       /* Determine if the input and output should be legacy-style */
+       if (*legacy_input_format != LEGACY_INPUT_FORMAT_NONE ||
+                       cfg->sources->len == 0 ||
+                       !bt_value_array_is_empty(legacy_input_paths) ||
+                       ctf_legacy_opts_is_any_set(ctf_legacy_opts)) {
+               legacy_input = true;
+       }
+
+       if (*legacy_output_format != LEGACY_OUTPUT_FORMAT_NONE ||
+                       cfg->sinks->len == 0 ||
+                       text_legacy_opts_is_any_set(text_legacy_opts)) {
+               legacy_output = true;
+       }
+
+       if (legacy_input) {
+               /* If no legacy input format was specified, default to CTF */
+               if (*legacy_input_format == LEGACY_INPUT_FORMAT_NONE) {
+                       *legacy_input_format = LEGACY_INPUT_FORMAT_CTF;
+               }
+
+               /* Make sure at least one input path exists */
+               if (bt_value_array_is_empty(legacy_input_paths)) {
+                       switch (*legacy_input_format) {
+                       case LEGACY_INPUT_FORMAT_CTF:
+                               printf_err("No input path specified for legacy \"ctf\" input format\n");
+                               break;
+                       case LEGACY_INPUT_FORMAT_LTTNG_LIVE:
+                               printf_err("No URL specified for legacy \"lttng-live\" input format\n");
+                               break;
+                       default:
+                               assert(false);
+                       }
+                       goto error;
+               }
+
+               /* Make sure no non-legacy sources are specified */
+               if (cfg->sources->len != 0) {
+                       print_input_legacy_to_sources(*legacy_input_format,
+                               legacy_input_paths, ctf_legacy_opts);
+                       goto error;
+               }
+       }
+
+       if (legacy_output) {
+               /*
+                * If no legacy output format was specified, default to
+                * "text".
+                */
+               if (*legacy_output_format == LEGACY_OUTPUT_FORMAT_NONE) {
+                       *legacy_output_format = LEGACY_OUTPUT_FORMAT_TEXT;
+               }
+
+               /*
+                * If any "text" option was specified, the output must be
+                * legacy "text".
+                */
+               if (text_legacy_opts_is_any_set(text_legacy_opts) &&
+                               *legacy_output_format !=
+                               LEGACY_OUTPUT_FORMAT_TEXT) {
+                       printf_err("Options for legacy \"text\" output format specified with a different legacy output format\n");
+                       goto error;
+               }
+
+               /* Make sure no non-legacy sinks are specified */
+               if (cfg->sinks->len != 0) {
+                       print_output_legacy_to_sinks(*legacy_output_format,
+                               text_legacy_opts);
+                       goto error;
+               }
+       }
+
+       /*
+        * If the output is the legacy "ctf-metadata" format, then the
+        * input should be the legacy "ctf" input format.
+        */
+       if (*legacy_output_format == LEGACY_OUTPUT_FORMAT_CTF_METADATA &&
+                       *legacy_input_format != LEGACY_INPUT_FORMAT_CTF) {
+               printf_err("Legacy \"ctf-metadata\" output format requires using legacy \"ctf\" input format\n");
+               goto error;
+       }
+
+       return true;
+
+error:
+       return false;
+}
+
+/*
+ * Parses a 64-bit signed integer.
+ *
+ * Returns a negative value if anything goes wrong.
+ */
+static
+int parse_int64(const char *arg, int64_t *val)
+{
+       char *endptr;
+
+       errno = 0;
+       *val = strtoll(arg, &endptr, 0);
+       if (*endptr != '\0' || arg == endptr || errno != 0) {
+               return -1;
+       }
+
+       return 0;
+}
+
+/* popt options */
+enum {
+       OPT_NONE = 0,
+       OPT_CLOCK_CYCLES,
+       OPT_CLOCK_DATE,
+       OPT_CLOCK_FORCE_CORRELATE,
+       OPT_CLOCK_GMT,
+       OPT_CLOCK_OFFSET,
+       OPT_CLOCK_OFFSET_NS,
+       OPT_CLOCK_SECONDS,
+       OPT_DEBUG,
+       OPT_DEBUG_INFO_DIR,
+       OPT_DEBUG_INFO_FULL_PATH,
+       OPT_DEBUG_INFO_TARGET_PREFIX,
+       OPT_FIELDS,
+       OPT_HELP,
+       OPT_HELP_LEGACY,
+       OPT_INPUT_FORMAT,
+       OPT_LIST,
+       OPT_NAMES,
+       OPT_NO_DELTA,
+       OPT_OUTPUT_FORMAT,
+       OPT_OUTPUT_PATH,
+       OPT_PLUGIN_PATH,
+       OPT_SINK,
+       OPT_SOURCE,
+       OPT_STREAM_INTERSECTION,
+       OPT_VERBOSE,
+       OPT_VERSION,
+};
+
+/* popt long option descriptions */
+static struct poptOption long_options[] = {
+       /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
+       { "clock-cycles", '\0', POPT_ARG_NONE, NULL, OPT_CLOCK_CYCLES, NULL, NULL },
+       { "clock-date", '\0', POPT_ARG_NONE, NULL, OPT_CLOCK_DATE, NULL, NULL },
+       { "clock-force-correlate", '\0', POPT_ARG_NONE, NULL, OPT_CLOCK_FORCE_CORRELATE, NULL, NULL },
+       { "clock-gmt", '\0', POPT_ARG_NONE, NULL, OPT_CLOCK_GMT, NULL, NULL },
+       { "clock-offset", '\0', POPT_ARG_STRING, NULL, OPT_CLOCK_OFFSET, NULL, NULL },
+       { "clock-offset-ns", '\0', POPT_ARG_STRING, NULL, OPT_CLOCK_OFFSET_NS, NULL, NULL },
+       { "clock-seconds", '\0', POPT_ARG_NONE, NULL, OPT_CLOCK_SECONDS, NULL, NULL },
+       { "debug", 'd', POPT_ARG_NONE, NULL, OPT_DEBUG, NULL, NULL },
+       { "debug-info-dir", 0, POPT_ARG_STRING, NULL, OPT_DEBUG_INFO_DIR, NULL, NULL },
+       { "debug-info-full-path", 0, POPT_ARG_NONE, NULL, OPT_DEBUG_INFO_FULL_PATH, NULL, NULL },
+       { "debug-info-target-prefix", 0, POPT_ARG_STRING, NULL, OPT_DEBUG_INFO_TARGET_PREFIX, NULL, NULL },
+       { "fields", 'f', POPT_ARG_STRING, NULL, OPT_FIELDS, NULL, NULL },
+       { "help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL },
+       { "help-legacy", '\0', POPT_ARG_NONE, NULL, OPT_HELP_LEGACY, NULL, NULL },
+       { "input-format", 'i', POPT_ARG_STRING, NULL, OPT_INPUT_FORMAT, NULL, NULL },
+       { "list", 'l', POPT_ARG_NONE, NULL, OPT_LIST, NULL, NULL },
+       { "names", 'n', POPT_ARG_STRING, NULL, OPT_NAMES, NULL, NULL },
+       { "no-delta", '\0', POPT_ARG_NONE, NULL, OPT_NO_DELTA, NULL, NULL },
+       { "output", 'w', POPT_ARG_STRING, NULL, OPT_OUTPUT_PATH, NULL, NULL },
+       { "output-format", 'o', POPT_ARG_STRING, NULL, OPT_OUTPUT_FORMAT, NULL, NULL },
+       { "plugin-path", 'p', POPT_ARG_STRING, NULL, OPT_PLUGIN_PATH, NULL, NULL },
+       { "sink", '\0', POPT_ARG_STRING, NULL, OPT_SINK, NULL, NULL },
+       { "source", '\0', POPT_ARG_STRING, NULL, OPT_SOURCE, NULL, NULL },
+       { "stream-intersection", '\0', POPT_ARG_NONE, NULL, OPT_STREAM_INTERSECTION, NULL, NULL },
+       { "verbose", 'v', POPT_ARG_NONE, NULL, OPT_VERBOSE, NULL, NULL },
+       { "version", 'V', POPT_ARG_NONE, NULL, OPT_VERSION, NULL, NULL },
+       { NULL, 0, 0, NULL, 0, NULL, NULL },
+};
+
+/*
+ * Sets the value of a given legacy offset option and marks it as set.
+ */
+static void set_offset_value(struct offset_opt *offset_opt, int64_t value)
+{
+       offset_opt->value = value;
+       offset_opt->is_set = true;
+}
+
+/*
+ * Returns a Babeltrace configuration, out of command-line arguments,
+ * containing everything that is needed to instanciate specific
+ * components with given parameters.
+ *
+ * *exit_code is set to the appropriate exit code to use as far as this
+ * function goes.
+ *
+ * Return value is NULL on error, otherwise it's owned by the caller.
+ */
+struct bt_config *bt_config_from_args(int argc, char *argv[], int *exit_code)
+{
+       struct bt_config *cfg = NULL;
+       poptContext pc = NULL;
+       char *arg = NULL;
+       struct ctf_legacy_opts ctf_legacy_opts = { 0 };
+       struct text_legacy_opts text_legacy_opts = { 0 };
+       enum legacy_input_format legacy_input_format = LEGACY_INPUT_FORMAT_NONE;
+       enum legacy_output_format legacy_output_format =
+               LEGACY_OUTPUT_FORMAT_NONE;
+       struct bt_value *legacy_input_paths = NULL;
+       int opt;
+
+       *exit_code = 0;
+
+       if (argc <= 1) {
+               print_usage(stdout);
+               goto end;
+       }
+
+       text_legacy_opts.output = g_string_new(NULL);
+       if (!text_legacy_opts.output) {
+               print_err_oom();
+               goto error;
+       }
+
+       text_legacy_opts.dbg_info_dir = g_string_new(NULL);
+       if (!text_legacy_opts.dbg_info_dir) {
+               print_err_oom();
+               goto error;
+       }
+
+       text_legacy_opts.dbg_info_target_prefix = g_string_new(NULL);
+       if (!text_legacy_opts.dbg_info_target_prefix) {
+               print_err_oom();
+               goto error;
+       }
+
+       /* Create config */
+       cfg = g_new0(struct bt_config, 1);
+       if (!cfg) {
+               print_err_oom();
+               goto error;
+       }
+
+       bt_object_init(cfg, bt_config_destroy);
+       cfg->sources = g_ptr_array_new_with_free_func((GDestroyNotify) bt_put);
+       if (!cfg->sources) {
+               print_err_oom();
+               goto error;
+       }
+
+       cfg->sinks = g_ptr_array_new_with_free_func((GDestroyNotify) bt_put);
+       if (!cfg->sinks) {
+               print_err_oom();
+               goto error;
+       }
+
+       legacy_input_paths = bt_value_array_create();
+       if (!legacy_input_paths) {
+               print_err_oom();
+               goto error;
+       }
+
+       /* Parse options */
+       pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0);
+       if (!pc) {
+               printf_err("Cannot get popt context\n");
+               goto error;
+       }
+
+       poptReadDefaultConfig(pc, 0);
+
+       while ((opt = poptGetNextOpt(pc)) > 0) {
+               arg = poptGetOptArg(pc);
+
+               switch (opt) {
+               case OPT_PLUGIN_PATH:
+                       if (cfg->plugin_paths) {
+                               printf_err("Duplicate --plugin-path option\n");
+                               goto error;
+                       }
+
+                       cfg->plugin_paths = plugin_paths_from_arg(arg);
+                       if (!cfg->plugin_paths) {
+                               printf_err("Invalid --plugin-path option's argument\n");
+                               goto error;
+                       }
+                       break;
+               case OPT_OUTPUT_PATH:
+                       if (text_legacy_opts.output->len > 0) {
+                               printf_err("Duplicate --output option\n");
+                               goto error;
+                       }
+
+                       g_string_assign(text_legacy_opts.output, arg);
+                       break;
+               case OPT_DEBUG_INFO_DIR:
+                       if (text_legacy_opts.dbg_info_dir->len > 0) {
+                               printf_err("Duplicate --debug-info-dir option\n");
+                               goto error;
+                       }
+
+                       g_string_assign(text_legacy_opts.dbg_info_dir, arg);
+                       break;
+               case OPT_DEBUG_INFO_TARGET_PREFIX:
+                       if (text_legacy_opts.dbg_info_target_prefix->len > 0) {
+                               printf_err("Duplicate --debug-info-target-prefix option\n");
+                               goto error;
+                       }
+
+                       g_string_assign(text_legacy_opts.dbg_info_target_prefix, arg);
+                       break;
+               case OPT_INPUT_FORMAT:
+               case OPT_SOURCE:
+               {
+                       struct bt_config_component *bt_config_component;
+
+                       if (opt == OPT_INPUT_FORMAT) {
+                               if (!strcmp(arg, "ctf")) {
+                                       /* Legacy CTF input format */
+                                       if (legacy_input_format) {
+                                               print_err_dup_legacy_input();
+                                               goto error;
+                                       }
+
+                                       legacy_input_format =
+                                               LEGACY_INPUT_FORMAT_CTF;
+                                       break;
+                               } else if (!strcmp(arg, "lttng-live")) {
+                                       /* Legacy LTTng-live input format */
+                                       if (legacy_input_format) {
+                                               print_err_dup_legacy_input();
+                                               goto error;
+                                       }
+
+                                       legacy_input_format =
+                                               LEGACY_INPUT_FORMAT_LTTNG_LIVE;
+                                       break;
+                               }
+                       }
+
+                       /* Non-legacy: try to create a component config */
+                       bt_config_component = bt_config_component_from_arg(arg);
+                       if (!bt_config_component) {
+                               printf_err("Invalid source component format:\n    %s\n",
+                                       arg);
+                               goto error;
+                       }
+
+                       g_ptr_array_add(cfg->sources, bt_config_component);
+                       break;
+               }
+               case OPT_OUTPUT_FORMAT:
+               case OPT_SINK:
+               {
+                       struct bt_config_component *bt_config_component;
+
+                       if (opt == OPT_OUTPUT_FORMAT) {
+                               if (!strcmp(arg, "text")) {
+                                       /* Legacy CTF-text output format */
+                                       if (legacy_output_format) {
+                                               print_err_dup_legacy_output();
+                                               goto error;
+                                       }
+
+                                       legacy_output_format =
+                                               LEGACY_OUTPUT_FORMAT_TEXT;
+                                       break;
+                               } else if (!strcmp(arg, "dummy")) {
+                                       /* Legacy dummy output format */
+                                       if (legacy_output_format) {
+                                               print_err_dup_legacy_output();
+                                               goto error;
+                                       }
+
+                                       legacy_output_format =
+                                               LEGACY_OUTPUT_FORMAT_DUMMY;
+                                       break;
+                               } else if (!strcmp(arg, "ctf-metadata")) {
+                                       /* Legacy CTF-metadata output format */
+                                       if (legacy_output_format) {
+                                               print_err_dup_legacy_output();
+                                               goto error;
+                                       }
+
+                                       legacy_output_format =
+                                               LEGACY_OUTPUT_FORMAT_CTF_METADATA;
+                                       break;
+                               }
+                       }
+
+                       /* Non-legacy: try to create a component config */
+                       bt_config_component = bt_config_component_from_arg(arg);
+                       if (!bt_config_component) {
+                               printf_err("Invalid sink component format:\n    %s\n",
+                                       arg);
+                               goto error;
+                       }
+
+                       g_ptr_array_add(cfg->sinks, bt_config_component);
+                       break;
+               }
+               case OPT_NAMES:
+                       if (text_legacy_opts.names) {
+                               printf_err("Duplicate --names option\n");
+                               goto error;
+                       }
+
+                       text_legacy_opts.names = names_from_arg(arg);
+                       if (!text_legacy_opts.names) {
+                               printf_err("Invalid --names option's argument\n");
+                               goto error;
+                       }
+                       break;
+               case OPT_FIELDS:
+                       if (text_legacy_opts.fields) {
+                               printf_err("Duplicate --fields option\n");
+                               goto error;
+                       }
+
+                       text_legacy_opts.fields = fields_from_arg(arg);
+                       if (!text_legacy_opts.fields) {
+                               printf_err("Invalid --fields option's argument\n");
+                               goto error;
+                       }
+                       break;
+               case OPT_NO_DELTA:
+                       text_legacy_opts.no_delta = true;
+                       break;
+               case OPT_CLOCK_CYCLES:
+                       text_legacy_opts.clock_cycles = true;
+                       break;
+               case OPT_CLOCK_SECONDS:
+                       text_legacy_opts.clock_seconds = true;
+                       break;
+               case OPT_CLOCK_DATE:
+                       text_legacy_opts.clock_date = true;
+                       break;
+               case OPT_CLOCK_GMT:
+                       text_legacy_opts.clock_gmt = true;
+                       break;
+               case OPT_DEBUG_INFO_FULL_PATH:
+                       text_legacy_opts.dbg_info_full_path = true;
+                       break;
+               case OPT_CLOCK_OFFSET:
+               {
+                       int64_t val;
+
+                       if (ctf_legacy_opts.offset_s.is_set) {
+                               printf_err("Duplicate --clock-offset option\n");
+                               goto error;
+                       }
+
+                       if (parse_int64(arg, &val)) {
+                               printf_err("Invalid --clock-offset option's argument\n");
+                               goto error;
+                       }
+
+                       set_offset_value(&ctf_legacy_opts.offset_s, val);
+                       break;
+               }
+               case OPT_CLOCK_OFFSET_NS:
+               {
+                       int64_t val;
+
+                       if (ctf_legacy_opts.offset_ns.is_set) {
+                               printf_err("Duplicate --clock-offset-ns option\n");
+                               goto error;
+                       }
+
+                       if (parse_int64(arg, &val)) {
+                               printf_err("Invalid --clock-offset-ns option's argument\n");
+                               goto error;
+                       }
+
+                       set_offset_value(&ctf_legacy_opts.offset_ns, val);
+                       break;
+               }
+               case OPT_STREAM_INTERSECTION:
+                       ctf_legacy_opts.stream_intersection = true;
+                       break;
+               case OPT_CLOCK_FORCE_CORRELATE:
+                       cfg->force_correlate = true;
+                       break;
+               case OPT_HELP:
+                       BT_PUT(cfg);
+                       print_usage(stdout);
+                       goto end;
+               case OPT_HELP_LEGACY:
+                       BT_PUT(cfg);
+                       print_legacy_usage(stdout);
+                       goto end;
+               case OPT_VERSION:
+                       BT_PUT(cfg);
+                       print_version();
+                       goto end;
+               case OPT_LIST:
+                       cfg->do_list = true;
+                       goto end;
+               case OPT_VERBOSE:
+                       cfg->verbose = true;
+                       break;
+               case OPT_DEBUG:
+                       cfg->debug = true;
+                       break;
+               default:
+                       printf_err("Unknown command-line option specified (option code %d)\n",
+                               opt);
+                       goto error;
+               }
+
+               free(arg);
+               arg = NULL;
+       }
+
+       /* Check for option parsing error */
+       if (opt < -1) {
+               printf_err("While parsing command-line options, at option %s: %s\n",
+                       poptBadOption(pc, 0), poptStrerror(opt));
+               goto error;
+       }
+
+       /* Consume leftover arguments as legacy input paths */
+       while (true) {
+               const char *input_path = poptGetArg(pc);
+
+               if (!input_path) {
+                       break;
+               }
+
+               if (bt_value_array_append_string(legacy_input_paths,
+                               input_path)) {
+                       print_err_oom();
+                       goto error;
+               }
+       }
+
+       /* Validate legacy/non-legacy options */
+       if (!validate_cfg(cfg, &legacy_input_format, &legacy_output_format,
+                       legacy_input_paths, &ctf_legacy_opts,
+                       &text_legacy_opts)) {
+               printf_err("Command-line options form an invalid configuration\n");
+               goto error;
+       }
+
+       /*
+        * If there's a legacy input format, convert it to source
+        * component configurations.
+        */
+       if (legacy_input_format) {
+               if (append_sources_from_legacy_opts(cfg->sources,
+                               legacy_input_format, &ctf_legacy_opts,
+                               legacy_input_paths)) {
+                       printf_err("Cannot convert legacy input format options to source(s)\n");
+                       goto error;
+               }
+       }
+
+       /*
+        * If there's a legacy output format, convert it to sink
+        * component configurations.
+        */
+       if (legacy_output_format) {
+               if (append_sinks_from_legacy_opts(cfg->sinks,
+                               legacy_output_format, &text_legacy_opts)) {
+                       printf_err("Cannot convert legacy output format options to sink(s)\n");
+                       goto error;
+               }
+       }
+
+       goto end;
+
+error:
+       BT_PUT(cfg);
+       cfg = NULL;
+       *exit_code = 1;
+
+end:
+       if (pc) {
+               poptFreeContext(pc);
+       }
+
+       if (text_legacy_opts.output) {
+               g_string_free(text_legacy_opts.output, TRUE);
+       }
+
+       if (text_legacy_opts.dbg_info_dir) {
+               g_string_free(text_legacy_opts.dbg_info_dir, TRUE);
+       }
+
+       if (text_legacy_opts.dbg_info_target_prefix) {
+               g_string_free(text_legacy_opts.dbg_info_target_prefix, TRUE);
+       }
+
+       free(arg);
+       BT_PUT(text_legacy_opts.names);
+       BT_PUT(text_legacy_opts.fields);
+       BT_PUT(legacy_input_paths);
+       return cfg;
+}
diff --git a/converter/babeltrace-cfg.h b/converter/babeltrace-cfg.h
new file mode 100644 (file)
index 0000000..c80a62d
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef BABELTRACE_CONVERTER_CFG_H
+#define BABELTRACE_CONVERTER_CFG_H
+
+/*
+ * Babeltrace trace converter - configuration
+ *
+ * Copyright 2016 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <babeltrace/values.h>
+#include <babeltrace/ref.h>
+#include <babeltrace/object-internal.h>
+#include <babeltrace/compiler.h>
+#include <glib.h>
+
+struct bt_config_component {
+       struct bt_object base;
+       GString *plugin_name;
+       GString *component_name;
+       struct bt_value *params;
+};
+
+struct bt_config {
+       struct bt_object base;
+       struct bt_value *plugin_paths;
+
+       /* Array of pointers to struct bt_config_component */
+       GPtrArray *sources;
+
+       /* Array of pointers to struct bt_config_component */
+       GPtrArray *sinks;
+
+       bool debug;
+       bool verbose;
+       bool do_list;
+       bool force_correlate;
+};
+
+static inline
+struct bt_config_component *bt_config_get_component(GPtrArray *array,
+               size_t index)
+{
+       return bt_get(g_ptr_array_index(array, index));
+}
+
+struct bt_config *bt_config_from_args(int argc, char *argv[], int *exit_code);
+
+#endif /* BABELTRACE_CONVERTER_CFG_H */
index e3436afa28a4272e180db25069363dd50c80e8b5..fed201e7d5fbe4cd228a598f8cc39f541d141320 100644 (file)
 #include <popt.h>
 #include <string.h>
 #include <stdio.h>
-
-
-static char *opt_plugin_path;
-static char *opt_input_path;
-
-enum {
-       OPT_NONE = 0,
-       OPT_PLUGIN_PATH,
-       OPT_INPUT_PATH,
-       OPT_VERBOSE,
-       OPT_DEBUG,
-       OPT_HELP,
-};
-
-/*
- * We are _not_ using POPT_ARG_STRING's ability to store directly into
- * variables, because we want to cast the return to non-const, which is
- * not possible without using poptGetOptArg explicitly. This helps us
- * controlling memory allocation correctly without making assumptions
- * about undocumented behaviors. poptGetOptArg is documented as
- * requiring the returned const char * to be freed by the caller.
- */
-static struct poptOption long_options[] = {
-       /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
-       { "plugin-path", 0, POPT_ARG_STRING, NULL, OPT_PLUGIN_PATH, NULL, NULL },
-       { "input-path", 0, POPT_ARG_STRING, NULL, OPT_INPUT_PATH, NULL, NULL },
-       { "verbose", 'v', POPT_ARG_NONE, NULL, OPT_VERBOSE, NULL, NULL },
-       { "debug", 'd', POPT_ARG_NONE, NULL, OPT_DEBUG, NULL, NULL },
-       { NULL, 0, 0, NULL, 0, NULL, NULL },
-};
-
-/*
- * Return 0 if caller should continue, < 0 if caller should return
- * error, > 0 if caller should exit without reporting error.
- */
-static int parse_options(int argc, char **argv)
-{
-       poptContext pc;
-       int opt, ret = 0;
-
-       if (argc == 1) {
-               return 1;       /* exit cleanly */
-       }
-
-       pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0);
-       poptReadDefaultConfig(pc, 0);
-
-       /* set default */
-       opt_context_field_names = 1;
-       opt_payload_field_names = 1;
-
-       while ((opt = poptGetNextOpt(pc)) != -1) {
-               switch (opt) {
-               case OPT_HELP:
-                       ret = 1;        /* exit cleanly */
-                       goto end;
-               case OPT_PLUGIN_PATH:
-                       opt_plugin_path = (char *) poptGetOptArg(pc);
-                       if (!opt_plugin_path) {
-                               ret = -EINVAL;
-                               goto end;
-                       }
-                       break;
-               case OPT_VERBOSE:
-                       babeltrace_verbose = 1;
-                       break;
-               case OPT_DEBUG:
-                       babeltrace_debug = 1;
-                       break;
-               case OPT_INPUT_PATH:
-                       opt_input_path = (char *) poptGetOptArg(pc);
-                       if (!opt_input_path) {
-                               ret = -EINVAL;
-                               goto end;
-                       }
-                       break;
-               default:
-                       ret = -EINVAL;
-                       goto end;
-               }
-       }
-
-end:
-       if (pc) {
-               poptFreeContext(pc);
-       }
-       return ret;
-}
+#include "babeltrace-cfg.h"
 
 static
 const char *component_type_str(enum bt_component_type type)
@@ -144,10 +57,12 @@ const char *component_type_str(enum bt_component_type type)
 }
 
 static
-void print_found_component_classes(struct bt_component_factory *factory)
+void print_component_classes_found(struct bt_component_factory *factory)
 {
        int count, i;
 
+       babeltrace_verbose = 1;
+
        if (!babeltrace_verbose) {
                return;
        }
@@ -194,49 +109,181 @@ void print_found_component_classes(struct bt_component_factory *factory)
        }
 }
 
+static
+void print_indent(size_t indent)
+{
+       size_t i;
+
+       for (i = 0; i < indent; i++) {
+               printf(" ");
+       }
+}
+
+static
+void print_value(struct bt_value *, size_t, bool);
+
+static
+bool print_map_value(const char *key, struct bt_value *object, void *data)
+{
+       size_t indent = (size_t) data;
+
+       print_indent(indent);
+       printf("\"%s\": ", key);
+       print_value(object, indent, false);
+
+       return true;
+}
+
+static
+void print_value(struct bt_value *value, size_t indent, bool do_indent)
+{
+       bool bool_val;
+       int64_t int_val;
+       double dbl_val;
+       const char *str_val;
+       int size;
+       int i;
+
+       if (!value) {
+               return;
+       }
+
+       if (do_indent) {
+               print_indent(indent);
+       }
+
+       switch (bt_value_get_type(value)) {
+       case BT_VALUE_TYPE_NULL:
+               printf("null\n");
+               break;
+       case BT_VALUE_TYPE_BOOL:
+               bt_value_bool_get(value, &bool_val);
+               printf("%s\n", bool_val ? "true" : "false");
+               break;
+       case BT_VALUE_TYPE_INTEGER:
+               bt_value_integer_get(value, &int_val);
+               printf("%" PRId64 "\n", int_val);
+               break;
+       case BT_VALUE_TYPE_FLOAT:
+               bt_value_float_get(value, &dbl_val);
+               printf("%lf\n", dbl_val);
+               break;
+       case BT_VALUE_TYPE_STRING:
+               bt_value_string_get(value, &str_val);
+               printf("\"%s\"\n", str_val);
+               break;
+       case BT_VALUE_TYPE_ARRAY:
+               size = bt_value_array_size(value);
+               printf("[\n");
+
+               for (i = 0; i < size; i++) {
+                       struct bt_value *element =
+                                       bt_value_array_get(value, i);
+
+                       print_value(element, indent + 2, true);
+                       BT_PUT(element);
+               }
+
+               print_indent(indent);
+               printf("]\n");
+               break;
+       case BT_VALUE_TYPE_MAP:
+               if (bt_value_map_is_empty(value)) {
+                       printf("{}\n");
+                       return;
+               }
+
+               printf("{\n");
+               bt_value_map_foreach(value, print_map_value,
+                       (void *) (indent + 2));
+               print_indent(indent);
+               printf("}\n");
+               break;
+       default:
+               assert(false);
+       }
+}
+
+static
+void print_bt_config_component(struct bt_config_component *bt_config_component)
+{
+       printf("  %s/%s\n", bt_config_component->plugin_name->str,
+               bt_config_component->component_name->str);
+       printf("    params:\n");
+       print_value(bt_config_component->params, 6, true);
+}
+
+static
+void print_bt_config_components(GPtrArray *array)
+{
+       size_t i;
+
+       for (i = 0; i < array->len; i++) {
+               struct bt_config_component *cfg_component =
+                       bt_config_get_component(array, i);
+               print_bt_config_component(cfg_component);
+               BT_PUT(cfg_component);
+       }
+}
+
+static
+void print_cfg(struct bt_config *cfg)
+{
+       printf("debug:           %d\n", cfg->debug);
+       printf("verbose:         %d\n", cfg->verbose);
+       printf("do list:         %d\n", cfg->do_list);
+       printf("force correlate: %d\n", cfg->force_correlate);
+       printf("plugin paths:\n");
+       print_value(cfg->plugin_paths, 2, true);
+       printf("sources:\n");
+       print_bt_config_components(cfg->sources);
+       printf("sinks:\n");
+       print_bt_config_components(cfg->sinks);
+}
+
 int main(int argc, char **argv)
 {
        int ret;
-       enum bt_value_status value_status;
        struct bt_component_factory *component_factory = NULL;
-       struct bt_component_class *source_class = NULL, *sink_class = NULL;
+       struct bt_component_class *source_class = NULL;
+       struct bt_component_class *sink_class = NULL;
        struct bt_component *source = NULL, *sink = NULL;
        struct bt_value *source_params = NULL, *sink_params = NULL;
+       struct bt_config *cfg;
        struct bt_notification_iterator *it = NULL;
        enum bt_component_status sink_status;
-
-       ret = parse_options(argc, argv);
-       if (ret < 0) {
-               fprintf(stderr, "Error parsing options.\n\n");
-               exit(EXIT_FAILURE);
-       } else if (ret > 0) {
-               exit(EXIT_SUCCESS);
-       }
-
-       source_params = bt_value_map_create();
-       if (!source_params) {
-               fprintf(stderr, "Failed to create source parameters map, aborting...\n");
-               ret = -1;
+       struct bt_value *first_plugin_path_value = NULL;
+       const char *first_plugin_path;
+       struct bt_config_component *cfg_component;
+
+       cfg = bt_config_from_args(argc, argv, &ret);
+       if (cfg) {
+               print_cfg(cfg);
+       } else {
                goto end;
        }
 
-       value_status = bt_value_map_insert_string(source_params, "path",
-                       opt_input_path);
-       if (value_status != BT_VALUE_STATUS_OK) {
+       /* TODO handle more than 1 source and 1 sink. */
+       if (cfg->sources->len != 1 || cfg->sinks->len != 1) {
+               fprintf(stderr, "Unexpected configuration, aborting.../n");
                ret = -1;
                goto end;
        }
+       cfg_component = bt_config_get_component(cfg->sources, 0);
+       source_params = bt_get(cfg_component->params);
+       BT_PUT(cfg_component);
+       cfg_component = bt_config_get_component(cfg->sinks, 0);
+       sink_params = bt_get(cfg_component->params);
+       BT_PUT(cfg_component);
 
        printf_verbose("Verbose mode active.\n");
        printf_debug("Debug mode active.\n");
 
-       if (!opt_plugin_path) {
+       if (bt_value_array_is_empty(cfg->plugin_paths)) {
                fprintf(stderr, "No plugin path specified, aborting...\n");
                ret = -1;
                goto end;
        }
-       printf_verbose("Looking-up plugins at %s\n",
-                       opt_plugin_path ? opt_plugin_path : "Invalid");
        component_factory = bt_component_factory_create();
        if (!component_factory) {
                fprintf(stderr, "Failed to create component factory.\n");
@@ -244,14 +291,17 @@ int main(int argc, char **argv)
                goto end;
        }
 
-       ret = bt_component_factory_load_recursive(component_factory, opt_plugin_path);
+       first_plugin_path_value = bt_value_array_get(cfg->plugin_paths, 0);
+       bt_value_string_get(first_plugin_path_value, &first_plugin_path);
+
+       ret = bt_component_factory_load_recursive(component_factory,
+                       first_plugin_path);
        if (ret) {
                fprintf(stderr, "Failed to load plugins.\n");
                goto end;
        }
 
-       print_found_component_classes(component_factory);
-
+       print_component_classes_found(component_factory);
        source_class = bt_component_factory_get_component_class(
                        component_factory, "ctf", BT_COMPONENT_TYPE_SOURCE,
                        "fs");
@@ -262,23 +312,23 @@ int main(int argc, char **argv)
        }
 
        sink_class = bt_component_factory_get_component_class(component_factory,
-                       "text", BT_COMPONENT_TYPE_SINK, "text");
+                       NULL, BT_COMPONENT_TYPE_SINK, "text");
        if (!sink_class) {
-               fprintf(stderr, "Could not find text sink component class. Aborting...\n");
+               fprintf(stderr, "Could not find text output component class. Aborting...\n");
                ret = -1;
                goto end;
        }
 
        source = bt_component_create(source_class, "ctf-fs", source_params);
        if (!source) {
-               fprintf(stderr, "Failed to instantiate source component. Aborting...\n");
-               ret = -1;
-               goto end;
-       }
+               fprintf(stderr, "Failed to instantiate ctf-fs source component. Aborting...\n");
+                ret = -1;
+                goto end;
+        }
 
-       sink = bt_component_create(sink_class, "bt_text_output", sink_params);
+       sink = bt_component_create(sink_class, "text", sink_params);
        if (!sink) {
-               fprintf(stderr, "Failed to instantiate output component. Aborting...\n");
+               fprintf(stderr, "Failed to instanciate text output component. Aborting...\n");
                ret = -1;
                goto end;
        }
@@ -286,9 +336,9 @@ int main(int argc, char **argv)
        it = bt_component_source_create_iterator(source);
        if (!it) {
                fprintf(stderr, "Failed to instantiate source iterator. Aborting...\n");
-               ret = -1;
-               goto end;
-       }
+                ret = -1;
+                goto end;
+        }
 
        sink_status = bt_component_sink_add_iterator(sink, it);
        if (sink_status != BT_COMPONENT_STATUS_OK) {
@@ -298,7 +348,6 @@ int main(int argc, char **argv)
 
        while (true) {
                sink_status = bt_component_sink_consume(sink);
-
                switch (sink_status) {
                case BT_COMPONENT_STATUS_AGAIN:
                        /* Wait for an arbitraty 500 ms. */
@@ -314,7 +363,6 @@ int main(int argc, char **argv)
                        goto end;
                }
        }
-       /* teardown and exit */
 end:
        BT_PUT(component_factory);
        BT_PUT(sink_class);
@@ -324,5 +372,7 @@ end:
        BT_PUT(source_params);
        BT_PUT(sink_params);
        BT_PUT(it);
+       BT_PUT(cfg);
+       BT_PUT(first_plugin_path_value);
        return ret ? 1 : 0;
 }
This page took 0.100985 seconds and 4 git commands to generate.