| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 Google Inc, Steven Rostedt <rostedt@goodmis.org> |
| */ |
| #include "ktrace.h" |
| |
| |
| static void create_usage(struct ccli *ccli) |
| { |
| ccli_printf(ccli, "usage: create <type> <type-command>\n" |
| " <type> : kprobe, eprobe, synthetic_event\n"); |
| } |
| |
| static int add_type_offset(struct ccli *ccli, char *p, char **pvar, char *line) |
| { |
| long long offset; |
| char *var = *pvar; |
| char *type; |
| char *tmp; |
| char *end; |
| bool neg; |
| int ret = 0; |
| |
| for (; *p; p++) { |
| switch(*p) { |
| case '.': |
| p++; |
| type = p; |
| for (; *p; p++) { |
| if (*p == '.' || *p == '-') |
| break; |
| } |
| if (*p == '.') { |
| ccli_printf(ccli, "# Two types can not be togethe '%s'\n", |
| line); |
| return -1; |
| } |
| if (strncmp(type, "string", 6) == 0 || |
| strncmp(type, "ustring", 7) == 0) { |
| if (*p) { |
| ccli_printf(ccli, "# Strings can not be deferenced '%s'\n", |
| type); |
| return -1; |
| } |
| ret = asprintf(&tmp, "+0(%s):%.*s", |
| var, (int)(p - type), |
| type); |
| } else { |
| ret = asprintf(&tmp, "%s:%.*s", |
| var, (int)(p - type), |
| type); |
| } |
| if (ret < 0) |
| return -1; |
| free(var); |
| var = tmp; |
| /* We need to compare current p again */ |
| p--; |
| break; |
| case '-': |
| p++; |
| if (*p != '>') { |
| ccli_printf(ccli, "# Invalid variable '%s'\n", |
| type); |
| return -1; |
| } |
| p++; |
| if (*p == '-') { |
| p++; |
| neg = true; |
| } |
| offset = strtoll(p, &end, 0); |
| ret = asprintf(&tmp, "%s%llu(%s)", |
| neg ? "-" : "+", offset, var); |
| if (ret < 0) |
| return -1; |
| free(var); |
| var = tmp; |
| break; |
| } |
| } |
| *pvar = var; |
| return ret; |
| } |
| |
| static int add_var(struct ccli *ccli, char **command, char *line) |
| { |
| char *sym; |
| char *var; |
| char *tmp; |
| char *p; |
| char ch; |
| int ret; |
| |
| p = strchr(line, '='); |
| if (!p) { |
| ccli_printf(ccli, "Invalid variable '%s'\n", line); |
| return -1; |
| } |
| |
| *p = '\0'; |
| sym = p + 1; |
| for (p++; *p; p++) { |
| if (*p == '.' || *p == '-') |
| break; |
| } |
| ch = *p; |
| *p = '\0'; |
| |
| var = strdup(sym); |
| if (!var) |
| return -1; |
| |
| *p = ch; |
| ret = add_type_offset(ccli, p, &var, line); |
| |
| if (ret >= 0) |
| ret = asprintf(&tmp, "%s %s=%s", *command, line, var); |
| |
| free(var); |
| if (ret < 0) |
| return -1; |
| free(*command); |
| *command = tmp; |
| return 0; |
| } |
| |
| static int create_kprobe(struct ccli *ccli, void *data, |
| int argc, char **argv) |
| { |
| char *command; |
| char *sym; |
| char *name; |
| int ret; |
| int i; |
| |
| if (argc < 3) { |
| ccli_printf(ccli, "# usage: create kprobe name function/address fields\n"); |
| return 0; |
| } |
| |
| name = argv[0]; |
| sym = argv[1]; |
| |
| ret = asprintf(&command, "k:%s %s", name, sym); |
| if (ret < 0) |
| return 0; |
| |
| for (i = 2 ; i < argc; i++ ) { |
| ret = add_var(ccli, &command, argv[i]); |
| if (ret < 0) |
| goto out; |
| } |
| ccli_printf(ccli, "# echo '%s' >> %s/dynamic_events\n", |
| command, tracefs_tracing_dir()); |
| out: |
| free(command); |
| return 0; |
| } |
| |
| static int add_event_var(struct ccli *ccli, struct tep_handle *tep, |
| char **command, struct tep_event *event, |
| char *line) |
| { |
| struct tep_format_field *field; |
| char *fname; |
| char *var; |
| char *tmp; |
| char *p; |
| char ch; |
| int ret; |
| |
| p = strchr(line, '='); |
| if (!p) { |
| ccli_printf(ccli, "Invalid variable '%s'\n", line); |
| return -1; |
| } |
| |
| *p = '\0'; |
| fname = p + 1; |
| for (p++; *p; p++) { |
| if (*p == '.' || *p == '-') |
| break; |
| } |
| ch = *p; |
| *p = '\0'; |
| |
| field = tep_find_any_field(event, fname); |
| if (!field) { |
| ccli_printf(ccli, "# Cannot find field '%s' for event '%s'\n", |
| fname, event->name); |
| return -1; |
| } |
| |
| ret = asprintf(&var, "$%s", fname); |
| if (ret < 0) |
| return -1; |
| |
| *p = ch; |
| ret = add_type_offset(ccli, p, &var, line); |
| |
| if (ret >= 0) |
| ret = asprintf(&tmp, "%s %s=%s", *command, line, var); |
| |
| free(var); |
| if (ret < 0) |
| return -1; |
| free(*command); |
| *command = tmp; |
| return 0; |
| } |
| |
| static int create_eprobe(struct ccli *ccli, void *data, |
| int argc, char **argv) |
| { |
| struct tep_handle *tep = data; |
| struct tep_event *event; |
| char *command; |
| char *system; |
| char *ename; |
| char *name; |
| char *sav; |
| int ret; |
| int i; |
| |
| if (argc < 3) { |
| ccli_printf(ccli, "# usage: create eprobe name system/event fields\n"); |
| return 0; |
| } |
| |
| name = argv[0]; |
| |
| system = strtok_r(argv[1], "/", &sav); |
| ename = strtok_r(NULL, "/", &sav); |
| if (!ename) { |
| event = tep_find_event_by_name(tep, NULL, system); |
| if (!event) { |
| ccli_printf(ccli, "# Event %s not found\n", system); |
| return 0; |
| } |
| system = event->system; |
| } else { |
| event = tep_find_event_by_name(tep, system, ename); |
| if (!event) { |
| ccli_printf(ccli, "# Event %s/%s not found\n", |
| system, ename); |
| return 0; |
| } |
| } |
| |
| ret = asprintf(&command, "e:%s %s/%s", name, system, ename); |
| if (ret < 0) |
| return 0; |
| |
| for (i = 2 ; i < argc; i++ ) { |
| ret = add_event_var(ccli, tep, &command, event, argv[i]); |
| if (ret < 0) |
| goto out; |
| } |
| ccli_printf(ccli, "# echo '%s' >> %s/dynamic_events\n", |
| command, tracefs_tracing_dir()); |
| out: |
| free(command); |
| return 0; |
| } |
| |
| static int usage_synthetic(struct ccli *ccli) |
| { |
| ccli_printf(ccli, "# usage: create synthetic name system/event.field1[,field2,..] system2/event2.field1[,field2,..] field1=systemX/eventX.field[ field2=systemX/eventX.field ...]\n" |
| "# Where the synthetic event is created when the fields of system/event\n" |
| "# match system/event2 fields. Then add the fields of the synthetic event\n" |
| "# on how they will map to the other fields.\n#\n" |
| "# A synthetic event field may also equal timestamps:\n" |
| "# start=system/event.TIMESTAMP\n#\n" |
| "# Or even a delta:\n" |
| "# delta=system/event.TIMESTAMP-system2/event2.TIMESTAMP\n" |
| ); |
| return 0; |
| } |
| |
| int add_synth_field(struct ccli *ccli, struct tracefs_synth *synth, |
| char *start_system, char *start_event, |
| char *end_system, char *end_event, char *field) |
| { |
| char *system; |
| char *event; |
| char *name; |
| char *val; |
| char *sav; |
| |
| name = strtok_r(field, "=", &sav); |
| system = strtok_r(NULL, "/", &sav); |
| if (!system) { |
| ccli_printf(ccli, "# Missing '=system/event.field' in '%s'\n", name); |
| return -1; |
| } |
| |
| event = strtok_r(NULL, ".", &sav); |
| if (!event) { |
| ccli_printf(ccli, "# Missing /event.field in '%s'\n", system); |
| return -1; |
| } |
| |
| val = strtok_r(NULL, ".", &sav); |
| if (!system) { |
| ccli_printf(ccli, "# Missing .field in '%s' \n", event); |
| return -1; |
| } |
| |
| if ((strcmp(system, start_system) == 0) && |
| (strcmp(event, start_event) == 0)) { |
| return tracefs_synth_add_start_field(synth, val, name); |
| } |
| |
| if ((strcmp(system, end_system) == 0) && |
| (strcmp(event, end_event) == 0)) { |
| return tracefs_synth_add_end_field(synth, val, name); |
| } |
| |
| ccli_printf(ccli, "# %s/%s does not match either start or end events\n", |
| system, event); |
| return -1; |
| } |
| |
| static int create_synthetic(struct ccli *ccli, void *data, |
| int argc, char **argv) |
| { |
| struct tep_handle *tep = data; |
| struct tracefs_synth *synth; |
| struct trace_seq seq; |
| char *system1; |
| char *event1; |
| char *system2; |
| char *event2; |
| char **fields1 = NULL; |
| char **fields2 = NULL; |
| char **svals = NULL; |
| char **tmp; |
| char *field; |
| char *name; |
| char *sav; |
| int ret; |
| int f = 0; |
| int i; |
| |
| if (argc < 4) |
| return usage_synthetic(ccli); |
| |
| name = argv[0]; |
| |
| system1 = strtok_r(argv[1], "/", &sav); |
| event1 = strtok_r(NULL, ".", &sav); |
| if (!system1 || !event1) { |
| ccli_printf(ccli, "# No slash found in first event %s\n", system1); |
| return 0; |
| } |
| |
| field = strtok_r(NULL, ",", &sav); |
| if (!field) { |
| ccli_printf(ccli, "# No field found for %s/%s\n", |
| system1, event1); |
| return 0; |
| } |
| |
| do { |
| tmp = realloc(fields1, sizeof(*tmp) * (f + 1)); |
| if (!tmp) |
| return 0; |
| tmp[f++] = field; |
| fields1 = tmp; |
| field = strtok_r(NULL, ",", &sav); |
| } while (field); |
| |
| /* Get the second event */ |
| system2 = strtok_r(argv[2], "/", &sav); |
| event2 = strtok_r(NULL, ".", &sav); |
| if (!system2 || !event2) { |
| ccli_printf(ccli, "# No slash found in second event %s\n", system2); |
| goto free; |
| } |
| |
| fields2 = calloc(f, sizeof(*fields2)); |
| if (!fields2) |
| goto free; |
| |
| for (i = 0; i < f; i++) { |
| field = strtok_r(NULL, ",", &sav); |
| if (!field) |
| goto free_fields; |
| fields2[i] = field; |
| } |
| field = strtok_r(NULL, ",", &sav); |
| if (field) |
| goto free_fields; |
| |
| svals = calloc(argc - 3, sizeof(*svals)); |
| if (!svals) |
| goto free; |
| |
| synth = tracefs_synth_alloc(tep, name, system1, event1, |
| system2, event2, fields1[0], |
| fields2[0], NULL); |
| if (!synth) { |
| ccli_printf(ccli, "# Failed to create synthetic event\n"); |
| goto free; |
| } |
| |
| for (i = 1; i < f; i++) { |
| ret = tracefs_synth_add_match_field(synth, fields1[i], |
| fields2[i], NULL); |
| if (ret < 0) { |
| ccli_printf(ccli, "# Failed to match %s with %s\n", |
| fields1[i], fields2[i]); |
| goto free_synth; |
| } |
| } |
| |
| for (i = 3; i < argc; i++) { |
| ret = add_synth_field(ccli, synth, system1, event1, |
| system2, event2, argv[i]); |
| if (ret < 0) |
| goto free_synth; |
| } |
| |
| trace_seq_init(&seq); |
| tracefs_synth_echo_cmd(&seq, synth); |
| trace_seq_terminate(&seq); |
| ccli_printf(ccli, "%s\n", seq.buffer); |
| trace_seq_destroy(&seq); |
| free_synth: |
| tracefs_synth_free(synth); |
| free: |
| free(fields1); |
| free(fields2); |
| free(svals); |
| return 0; |
| free_fields: |
| ccli_printf(ccli, "# End event must have same number of fields as start event\n"); |
| goto free; |
| } |
| |
| int cmd_create(struct ccli *ccli, const char *command, const char *line, |
| void *data, int argc, char **argv) |
| { |
| if (argc < 2) { |
| create_usage(ccli); |
| return 0; |
| } |
| |
| if (strcmp(argv[1], "kprobe") == 0) |
| return create_kprobe(ccli, data, argc - 2, argv + 2); |
| |
| if (strcmp(argv[1], "eprobe") == 0) |
| return create_eprobe(ccli, data, argc - 2, argv + 2); |
| |
| if (strcmp(argv[1], "synthetic") == 0) |
| return create_synthetic(ccli, data, argc - 2, argv + 2); |
| |
| return 0; |
| } |
| |
| static int get_mods(char ***mods) |
| { |
| FILE *fp; |
| char **list = NULL; |
| char **tmp; |
| char *line = NULL; |
| char *mod; |
| char *sav; |
| size_t len = 0; |
| size_t mlen; |
| int cnt = 0; |
| |
| fp = fopen("/proc/modules", "r"); |
| if (!fp) |
| return 0; |
| |
| while (getline(&line, &len, fp) > 0) { |
| mod = strtok_r(line, " ", &sav); |
| tmp = realloc(list, sizeof(*list) * (cnt + 1)); |
| if (!tmp) { |
| for (cnt--; cnt >= 0; cnt--) |
| free(list[cnt]); |
| free(list); |
| free(line); |
| return 0; |
| } |
| tmp[cnt++] = mod; |
| mlen = strlen(mod); |
| /* Append the ":" on modules to denote these are modules */ |
| if (mlen + 2 < len) { |
| mod[mlen++] = ':'; |
| mod[mlen] = '\0'; |
| } |
| list = tmp; |
| line = NULL; |
| len = 0; |
| } |
| free(line); |
| *mods = list; |
| fclose(fp); |
| return cnt; |
| } |
| |
| static int load_available_filter(char ***list, char *match) |
| { |
| char **funcs = NULL; |
| char **mods = NULL; |
| char *filter = NULL; |
| char *mod = NULL; |
| char *p; |
| int ret; |
| int cnt = 0; |
| int mcnt = 0; |
| int i; |
| |
| *list = NULL; |
| if ((p = strchr(match, ':'))) { |
| mod = strdup(match); |
| if (!mod) |
| return 0; |
| mod[p - match] = '\0'; |
| match = p + 1; |
| } else { |
| mcnt = get_mods(&mods); |
| } |
| ret = asprintf(&filter, "%s*", match); |
| if (ret < 0) |
| goto out; |
| |
| ret = tracefs_filter_functions(filter, mod, &funcs); |
| if (ret < 0) |
| goto out; |
| |
| cnt = tracefs_list_size(funcs); |
| if (cnt < 0) |
| goto out; |
| |
| *list = calloc(cnt + mcnt, sizeof(**list)); |
| for (i = 0; i < mcnt; i++) { |
| (*list)[i] = mods[i]; |
| mods[i] = NULL; |
| } |
| |
| for (i = 0; i < cnt; i++) { |
| if (mod) { |
| ret = asprintf(&p, "%s:%s", mod, funcs[i]); |
| if (ret < 0) |
| p = NULL; |
| } else |
| p = strdup(funcs[i]); |
| (*list)[i + mcnt] = p; |
| } |
| out: |
| tracefs_list_free(funcs); |
| free(filter); |
| for (i = 0; i < mcnt; i++) |
| free(mods[i]); |
| free(mods); |
| free(mod); |
| /* It's possible to get here without allocating list */ |
| return *list ? cnt + mcnt : 0; |
| } |
| |
| static int type_completion(char ***list, char *match, int len) |
| { |
| static char *types[] = {"string" , "ustring", "x8", "x16", "x32", "x64", |
| "u8", "u16", "u32", "u64", "s8", "s16", "s32", "s64" }; |
| char **words; |
| int i, x; |
| |
| x = ARRAY_SIZE(types); |
| words = calloc(x, sizeof(char *)); |
| if (!words) |
| return 0; |
| for (i = 0; i < x; i++) { |
| asprintf(&words[i], "%.*s.%s", |
| len, match, types[i]); |
| } |
| *list = words; |
| match[strlen(match)] = CCLI_NOSPACE; |
| return x; |
| } |
| |
| static int offset_completion(char m, char ***list, char *match, int len) |
| { |
| char **words; |
| |
| if (!m) { |
| words = calloc(1, sizeof(char *)); |
| if (!words) |
| return 0; |
| asprintf(&words[0], "%.*s->", |
| len, match); |
| *list = words; |
| match[strlen(match)] = CCLI_NOSPACE; |
| return 1; |
| } |
| return 0; |
| } |
| static int kprobe_completion(struct ccli *ccli, void *data, |
| int argc, char **argv, |
| char ***list, int word, char *match) |
| { |
| char *p, *m; |
| int len; |
| int ret; |
| |
| switch (word) { |
| case 0: |
| ccli_printf(ccli, "\n# Name the kprobe\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| case 1: |
| if (!strlen(match)) { |
| ccli_printf(ccli, "\n# [mod:]function-name[+offset] | @memory-address\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| if (match[0] == '@') { |
| ccli_printf(ccli, "\n# @memory-address\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| if (strchr(match, '+')) { |
| ccli_printf(ccli, "\n# [mod:]function-name+offset\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| ret = load_available_filter(list, match); |
| match[strlen(match)] = CCLI_NOSPACE; |
| return ret; |
| default: |
| p = strchr(match, '='); |
| if (!p) { |
| ccli_printf(ccli, "\n# var=ARG[.type][->offset[.type]\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| m = p; |
| while (*p) { |
| if (*p == '.' || *p == '-') |
| m = p; |
| p++; |
| } |
| |
| len = m - match; |
| |
| switch (*m) { |
| case '.': |
| return type_completion(list, match, len); |
| case '-': |
| return offset_completion(m[1], list, match, len); |
| } |
| } |
| return 0; |
| } |
| |
| static int event_completion(struct ccli *ccli, struct tep_handle *tep, |
| char ***list, char *match, char append) |
| { |
| char **systems; |
| char **events; |
| char **words; |
| char *system; |
| char *event; |
| char *p; |
| int i; |
| |
| p = strchr(match, '/'); |
| if (p) { |
| system = strdup(match); |
| if (!system) |
| return 0; |
| system[p - match] = '\0'; |
| events = tracefs_system_events(NULL, system); |
| if (!events) { |
| free(system); |
| return 0; |
| } |
| words = calloc(tracefs_list_size(events), sizeof(char *)); |
| i = 0; |
| if (words) { |
| for (; events[i]; i++) { |
| asprintf(&event, "%s/%s", system, events[i]); |
| if (append && event && !strcmp(event, match)) { |
| asprintf(words + i, "%s.", event); |
| free(event); |
| } else |
| words[i] = event; |
| } |
| } |
| tracefs_list_free(events); |
| } else { |
| systems = tracefs_event_systems(NULL); |
| if (!systems) |
| return 0; |
| words = calloc(tracefs_list_size(systems), sizeof(char *)); |
| i = 0; |
| if (words) { |
| for (; systems[i]; i++) |
| words[i] = strdup(systems[i]); |
| } |
| tracefs_list_free(systems); |
| /* Use '/' as a delim */ |
| match[strlen(match)] = '/'; |
| } |
| *list = words; |
| return i; |
| } |
| |
| static struct tep_event *find_event(struct tep_handle *tep, char *ename) |
| { |
| struct tep_event *event; |
| char *system; |
| char *name; |
| char *sav; |
| |
| system = strtok_r(ename, "/", &sav); |
| name = strtok_r(NULL, "/", &sav); |
| if (!system || !name) |
| return NULL; |
| |
| event = tep_find_event_by_name(tep, system, name); |
| /* put back ename to how we found it */ |
| sav = system + strlen(system); |
| *sav = '/'; |
| return event; |
| } |
| |
| static int field_completion(struct tep_event *event, char ***list, char *prefix) |
| { |
| struct tep_format_field **common_fields; |
| struct tep_format_field **fields; |
| char **words; |
| char **tmp; |
| int i, x; |
| |
| common_fields = tep_event_common_fields(event); |
| fields = tep_event_fields(event); |
| words = NULL; |
| x = 0; |
| for (i = 0; common_fields && common_fields[i]; i++) { |
| tmp = realloc(words, sizeof(char *) * (x + 2)); |
| if (!tmp) { |
| ccli_argv_free(words); |
| return 0; |
| } |
| words = tmp; |
| asprintf(&words[x++], "%s%s", prefix, common_fields[i]->name); |
| words[x] = NULL; |
| } |
| for (i = 0; fields && fields[i]; i++) { |
| tmp = realloc(words, sizeof(char *) * (x + 2)); |
| if (!tmp) { |
| ccli_argv_free(words); |
| return 0; |
| } |
| words = tmp; |
| asprintf(&words[x++], "%s%s", prefix, fields[i]->name); |
| words[x] = NULL; |
| } |
| free(common_fields); |
| free(fields); |
| *list = words; |
| return x; |
| } |
| |
| static int eprobe_completion(struct ccli *ccli, void *data, |
| int argc, char **argv, |
| char ***list, int word, char *match) |
| { |
| struct tep_handle *tep = data; |
| struct tep_event *event; |
| char *prefix; |
| char *p, *m; |
| int len; |
| int ret; |
| |
| switch (word) { |
| case 0: |
| ccli_printf(ccli, "\n# Name the event probe\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| case 1: |
| return event_completion(ccli, tep, list, match, 0); |
| default: |
| event = find_event(tep, argv[1]); |
| if (!event) { |
| ccli_printf(ccli, "\n# Event %s not found\n", argv[1]); |
| return 0; |
| } |
| p = strchr(match, '='); |
| if (!p) { |
| ccli_printf(ccli, "\n# var=field[.type][->offset[.type]\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| m = p; |
| while (*p) { |
| if (*p == '.' || *p == '-') |
| m = p; |
| p++; |
| } |
| |
| len = m - match; |
| |
| switch (*m) { |
| default: |
| asprintf(&prefix, "%.*s=", len, match); |
| if (!prefix) |
| return 0; |
| ret = field_completion(event, list, prefix); |
| free(prefix); |
| if (ret > 0) |
| match[strlen(match)] = CCLI_NOSPACE; |
| return ret; |
| case '.': |
| return type_completion(list, match, len); |
| case '-': |
| return offset_completion(m[1], list, match, len); |
| } |
| } |
| printf("\neprobe word=%d match=%s\n", word, match); |
| return 0; |
| } |
| |
| static int append_event_field(struct ccli *ccli, struct tep_handle *tep, char ***list, |
| char *ename, char *match) |
| { |
| struct tep_event *event; |
| char *p; |
| |
| /* Find the end of the event */ |
| for (p = ename; *p && *p != '.'; p++) |
| ; |
| if (!*p) // Should not happen! |
| return 0; |
| *p = '\0'; |
| |
| event = find_event(tep, ename); |
| if (!event) { |
| ccli_printf(ccli, "\n# Event %s not found\n", ename); |
| return 0; |
| } |
| *p = '.'; |
| /* Find the start of the last field in the comma separated list */ |
| for (p += strlen(p) - 1; *p != '.' && *p != ',' ; p--) |
| ; |
| p++; |
| *p = '\0'; |
| |
| return field_completion(event, list, ename); |
| } |
| |
| static int append_field_ts(struct ccli *ccli, struct tep_handle *tep, char ***list, |
| char *ename) |
| { |
| struct tep_event *event; |
| char **words; |
| char *e; |
| char *p; |
| int ret; |
| |
| /* Find start of the event */ |
| e = strchr(ename, '='); |
| if (!e) // should not happen |
| return 0; |
| e++; |
| |
| /* Find the end of the event */ |
| for (p = e; *p && *p != '.'; p++) |
| ; |
| if (!*p) // Should not happen! |
| return 0; |
| *p = '\0'; |
| |
| event = find_event(tep, e); |
| if (!event) { |
| ccli_printf(ccli, "\n# Event %s not found\n", e); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| |
| *p = '.'; |
| p[1] = '\0'; |
| |
| ret = field_completion(event, list, ename); |
| |
| if (ret > 0) { |
| words = realloc(*list, sizeof(*words) * (ret + 1)); |
| if (words) { |
| asprintf(words + ret, "%sTIMESTAMP", ename); |
| *list = words; |
| ret++; |
| } |
| } |
| return ret; |
| } |
| |
| static int synthetic_completion(struct ccli *ccli, void *data, |
| int argc, char **argv, |
| char ***list, int word, char *match) |
| { |
| struct tep_handle *tep = data; |
| struct tep_event *event1, *event2; |
| char **words; |
| char *p; |
| int len; |
| int ret; |
| |
| switch (word) { |
| case 0: |
| ccli_printf(ccli, "\n# Name the synthetic event\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| case 1: |
| case 2: |
| len = strlen(match); |
| |
| /* See if this already an event */ |
| if (strchr(match, '.')) |
| return append_event_field(ccli, tep, list, argv[word], match); |
| |
| ret = event_completion(ccli, tep, list, match, '.'); |
| /* Do not add a space if this is an event */ |
| if (ret > 0 && !match[len]) |
| match[len] = CCLI_NOSPACE; |
| return ret; |
| default: |
| p = strchr(match, '='); |
| if (!p) { |
| ccli_printf(ccli, "\n# field=system/event.[field|TIMESTAMP]\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| if (strchr(p, '.')) |
| return append_field_ts(ccli, tep, list, argv[word]); |
| |
| argv[word][p - match] = '\0'; |
| |
| /* Can only be one of the previous events */ |
| p = strchr(argv[1], '.'); |
| if (!p) { |
| ccli_printf(ccli, "# %s needs a field\n", argv[1]); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| *p = '\0'; |
| event1 = find_event(tep, argv[1]); |
| if (!event1) { |
| ccli_printf(ccli, "\n# Event %s not found\n", argv[1]); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| p = strchr(argv[2], '.'); |
| if (!p) { |
| ccli_printf(ccli, "\n# %s needs a field\n", argv[2]); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| *p = '\0'; |
| event2 = find_event(tep, argv[2]); |
| if (!event2) { |
| ccli_printf(ccli, "\n# Event %s not found\n", argv[2]); |
| ccli_line_refresh(ccli); |
| return 0; |
| } |
| |
| words = calloc(2, sizeof(*words)); |
| if (!words) |
| return 0; |
| asprintf(words, "%s=%s/%s", argv[word], |
| event1->system, event1->name); |
| asprintf(words + 1, "%s=%s/%s", argv[word], |
| event2->system, event2->name); |
| *list = words; |
| match[strlen(match)] = '.'; |
| |
| return 2; |
| } |
| printf("\neprobe word=%d match=%s\n", word, match); |
| return 0; |
| } |
| |
| int create_completion(struct ccli *ccli, const char *command, |
| const char *line, int word, |
| char *match, char ***list, void *data) |
| { |
| char *types[] = { "kprobe", "eprobe", "synthetic" }; |
| char **words; |
| char **argv; |
| int argc; |
| int ret = 0; |
| int i; |
| |
| if (word == 1) { |
| words = calloc(ARRAY_SIZE(types), sizeof(char *)); |
| if (!words) |
| return 0; |
| for (i = 0; i < ARRAY_SIZE(types); i++) |
| words[i] = strdup(types[i]); |
| *list = words; |
| return i; |
| } |
| |
| argc = ccli_line_parse(line, &argv); |
| if (argc < 0) |
| return 0; |
| |
| if (strcmp(argv[1], "kprobe") == 0) |
| ret = kprobe_completion(ccli, data, argc - 2, argv + 2, |
| list, word - 2, match); |
| |
| if (strcmp(argv[1], "eprobe") == 0) |
| ret = eprobe_completion(ccli, data, argc - 2, argv + 2, |
| list, word - 2, match); |
| |
| if (strcmp(argv[1], "synthetic") == 0) |
| ret = synthetic_completion(ccli, data, argc - 2, argv + 2, |
| list, word - 2, match); |
| |
| ccli_argv_free(argv); |
| |
| return ret; |
| } |