| // 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 create_kprobe(struct ccli *ccli, void *data, |
| int argc, char **argv) |
| { |
| 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; |
| long long offset; |
| bool neg; |
| char *fname; |
| char *type; |
| char *var; |
| char *tmp; |
| char *end; |
| 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; |
| 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); |
| goto out; |
| } |
| if (strncmp(type, "string", 6) == 0 || |
| strncmp(type, "ustring", 7) == 0) { |
| if (*p) { |
| ccli_printf(ccli, "# Strings can not be deferenced '%s'\n", |
| type); |
| goto out; |
| } |
| 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) |
| goto out; |
| 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); |
| goto out; |
| } |
| p++; |
| if (*p == '-') { |
| p++; |
| neg = true; |
| } |
| offset = strtoll(p, &end, 0); |
| ret = asprintf(&tmp, "%s%llu(%s)", |
| neg ? "-" : "+", offset, var); |
| if (ret < 0) |
| goto out; |
| free(var); |
| var = tmp; |
| break; |
| } |
| } |
| /* Finished */ |
| ret = asprintf(&tmp, "%s %s=%s", *command, line, var); |
| out: |
| 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; |
| } |
| |
| 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); |
| |
| return 0; |
| } |
| |
| static int kprobe_completion(struct ccli *ccli, void *data, |
| int argc, char **argv, |
| char ***list, int word, char *match) |
| { |
| return 0; |
| } |
| |
| 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_format_field **common_fields; |
| struct tep_format_field **fields; |
| struct tep_event *event; |
| static char *types[] = {"string" , "ustring", "x8", "x16", "x32", "x64", |
| "u8", "u16", "u32", "u64", "s8", "s16", "s32", "s64" }; |
| char **systems; |
| char **events; |
| char **words; |
| char **tmp; |
| char *system; |
| char *name; |
| char *sav; |
| char *p, *m; |
| int len; |
| int i, x; |
| |
| switch (word) { |
| case 0: |
| ccli_printf(ccli, "\n# Name the event probe\n"); |
| ccli_line_refresh(ccli); |
| return 0; |
| case 1: |
| 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(words + i, "%s/%s", |
| system, events[i]); |
| } |
| 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; |
| default: |
| system = strtok_r(argv[1], "/", &sav); |
| name = strtok_r(NULL, "/", &sav); |
| if (!system || !name) |
| return 0; |
| event = tep_find_event_by_name(tep, system, name); |
| if (!event) { |
| ccli_printf(ccli, "\n# Event %s/%s not found\n", |
| system, name); |
| 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: |
| 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", |
| len, match, |
| 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", |
| len, match, |
| fields[i]->name); |
| words[x] = NULL; |
| } |
| free(common_fields); |
| free(fields); |
| *list = words; |
| match[strlen(match)] = CCLI_NOSPACE; |
| return x; |
| case '.': |
| 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; |
| case '-': |
| if (!m[1]) { |
| 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; |
| } |
| } |
| 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); |
| |
| ccli_argv_free(argv); |
| |
| return ret; |
| } |