lkml.org 
[lkml]   [2020]   [Dec]   [17]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v4 4/5] tracing: Add a backward-compatibility check for synthetic event creation
Date
The synthetic event parsing rework requiring semicolons between
synthetic event fields. That requirement breaks existing users who
might already have used the old form, so this adds a pre-parsing pass
that adds semicolons between fields to any string missing them. If
none are required, the original string is used.

In the future, if/when new features are added, the requirement will be
that any string containing the new feature will be required to use
semicolons, and the audit_old_buffer() check can check for those and
avoid the pre-parsing semicolon pass altogether.

As it stands, the pre-parsing pass creates a new string with
semicolons only if one or more semicolons were actually needed and
only if no errors were found in pre-parsing. The assumption is that
the real parsing pass will find and flag any errors and the user
should see them in reference to the original unmodified string.

Signed-off-by: Tom Zanussi <zanussi@kernel.org>
---
kernel/trace/trace_events_synth.c | 294 ++++++++++++++++++++++++++++--
1 file changed, 274 insertions(+), 20 deletions(-)

diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 2a9c8bf74bb2..6bff54ed31ce 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -1373,6 +1373,245 @@ int synth_event_delete(const char *event_name)
}
EXPORT_SYMBOL_GPL(synth_event_delete);

+static int save_synth_field(int argc, char **argv, int *consumed,
+ struct seq_buf *s, bool *added_semi)
+{
+ const char *prefix = NULL, *field_name, *field_type = argv[0];
+ const char *save_field_type, *array, *next_tok;
+ int len, ret = -EINVAL;
+ struct seq_buf f;
+ ssize_t size;
+ char *tmp;
+
+ *added_semi = false;
+
+ if (field_type[0] == ';')
+ field_type++;
+
+ if (!strcmp(field_type, "unsigned")) {
+ if (argc < 3)
+ goto out;
+ prefix = "unsigned";
+ field_type = argv[1];
+ field_name = argv[2];
+ *consumed = 3;
+ } else {
+ field_type = argv[0];
+ field_name = argv[1];
+ *consumed = 2;
+ }
+
+ len = strlen(field_name);
+ array = strchr(field_name, '[');
+ if (array)
+ len -= strlen(array);
+ else if (field_name[len - 1] == ';')
+ len--;
+
+ tmp = kmemdup_nul(field_name, len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (!is_good_name(tmp)) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ save_field_type = field_type;
+ if (field_type[0] == ';')
+ field_type++;
+ len = strlen(field_type) + 1;
+
+ if (array)
+ len += strlen(array);
+
+ if (prefix)
+ len += strlen(prefix) + 1;
+
+ tmp = kzalloc(len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ seq_buf_init(&f, tmp, len);
+ if (prefix) {
+ seq_buf_puts(&f, prefix);
+ seq_buf_putc(&f, ' ');
+ }
+ seq_buf_puts(&f, field_type);
+ if (array) {
+ seq_buf_puts(&f, array);
+ if (f.buffer[f.len - 1] == ';')
+ f.len--;
+ }
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&f))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ f.buffer[f.len] = '\0';
+
+ field_type = save_field_type;
+
+ size = synth_field_size(tmp);
+ if (size < 0 || ((size == 0) && (!synth_field_is_string(tmp)))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ if (prefix) {
+ seq_buf_puts(s, prefix);
+ seq_buf_putc(s, ' ');
+ }
+ seq_buf_puts(s, field_type);
+ seq_buf_putc(s, ' ');
+ seq_buf_puts(s, field_name);
+ if (field_name[strlen(field_name) - 1] == ';')
+ seq_buf_putc(s, ' ');
+
+ if (*consumed < argc) {
+ next_tok = argv[*consumed];
+ if (field_name[strlen(field_name) - 1] != ';' &&
+ next_tok[0] != ';') {
+ seq_buf_puts(s, "; ");
+ *added_semi = true;
+ }
+ }
+
+ ret = 0;
+ out:
+ return ret;
+}
+
+static char *insert_semicolons(const char *raw_command)
+{
+ int i, argc, consumed = 0, n_fields = 0, semis_added = 0;
+ char *name, **argv, **save_argv;
+ int ret = -EINVAL;
+ struct seq_buf s;
+ bool added_semi;
+ char *buf;
+
+ argc = 0;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return NULL;
+
+ if (!argc)
+ goto out;
+
+ name = argv[0];
+ save_argv = argv;
+ argv++;
+ argc--;
+
+ buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ seq_buf_init(&s, buf, MAX_DYNEVENT_CMD_LEN);
+
+ seq_buf_puts(&s, name);
+ seq_buf_putc(&s, ' ');
+
+ if (name[0] == '\0' || argc < 1)
+ goto err;
+
+ for (i = 0; i < argc - 1; i++) {
+ if (strcmp(argv[i], ";") == 0) {
+ seq_buf_puts(&s, " ; ");
+ continue;
+ }
+
+ if (n_fields == SYNTH_FIELDS_MAX)
+ goto err;
+
+ ret = save_synth_field(argc - i, &argv[i], &consumed,
+ &s, &added_semi);
+ if (ret)
+ goto err;
+
+ if (added_semi)
+ semis_added++;
+
+ i += consumed - 1;
+ }
+
+ if (i < argc && strcmp(argv[i], ";") != 0)
+ goto err;
+
+ if (!semis_added) {
+ kfree(buf);
+ buf = NULL;
+ goto out;
+ }
+
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&s)))
+ goto err;
+
+ buf[s.len] = '\0';
+ out:
+ argv_free(save_argv);
+
+ return buf;
+ err:
+ kfree(buf);
+ buf = ERR_PTR(ret);
+
+ goto out;
+}
+
+static bool audit_old_buffer(const char *cmd)
+{
+ /* as of now, every cmd is an old cmd */
+ return true;
+}
+
+/**
+ * get_parseable_cmd - Return a modifiable string for parsing
+ * @raw_command: The command to start with
+ *
+ * Create a cmd string that can be modified by the caller for command
+ * parsing purposes. If successful, the caller must free the command
+ * returned.
+ *
+ * The input string will first be checked to see whether or not it can
+ * be considered an 'old command' - a command that doesn't require
+ * semicolons between fields - for which backward compatibility must
+ * be maintained. If it can be considered an old command, a semicolon
+ * will be added between any two fields missing one. If no semicolons
+ * were required, or if the preparsing required for the pass
+ * encountered errors, a modifiable copy of the original string will
+ * be returned.
+ *
+ * Return: parseable cmd if successful, error or NULL string otherwise.
+ */
+static char *get_parseable_cmd(const char *raw_command)
+{
+ char *cmd = NULL;
+
+ if (audit_old_buffer(raw_command))
+ cmd = insert_semicolons(raw_command);
+
+ if (IS_ERR_OR_NULL(cmd)) {
+ cmd = kstrdup(raw_command, GFP_KERNEL);
+ if (!cmd)
+ cmd = ERR_PTR(-ENOMEM);
+ }
+
+ return cmd;
+}
+
static int check_command(const char *raw_command)
{
char **argv = NULL, *cmd, *saved_cmd, *name_and_field;
@@ -1406,28 +1645,33 @@ static int check_command(const char *raw_command)

static int create_or_delete_synth_event(const char *raw_command)
{
- char *name = NULL, *fields, *p;
+ char *name = NULL, *fields, *p, *cmd;
int ret = 0;

raw_command = skip_spaces(raw_command);
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);

- ret = check_command(raw_command);
+ last_cmd_set(cmd);
+
+ ret = check_command(cmd);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

- name = kmemdup_nul(raw_command, p - raw_command, GFP_KERNEL);
+ name = kmemdup_nul(cmd, p - cmd, GFP_KERNEL);
if (!name)
return -ENOMEM;

@@ -1441,6 +1685,7 @@ static int create_or_delete_synth_event(const char *raw_command)
ret = __create_synth_event(name, fields);
free:
kfree(name);
+ kfree(cmd);

return ret;
}
@@ -1988,7 +2233,7 @@ EXPORT_SYMBOL_GPL(synth_event_trace_end);

static int create_synth_event(const char *raw_command)
{
- char *fields, *p;
+ char *fields, *p, *cmd;
const char *name;
int len, ret = 0;

@@ -1996,20 +2241,27 @@ static int create_synth_event(const char *raw_command)
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ last_cmd_set(cmd);

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

fields = skip_spaces(p);

- name = raw_command;
+ name = cmd;

- if (name[0] != 's' || name[1] != ':')
- return -ECANCELED;
+ if (name[0] != 's' || name[1] != ':') {
+ ret = -ECANCELED;
+ goto free;
+ }
name += 2;

/* This interface accepts group name prefix */
@@ -2017,26 +2269,28 @@ static int create_synth_event(const char *raw_command)
len = str_has_prefix(name, SYNTH_SYSTEM "/");
if (len == 0) {
synth_err(SYNTH_ERR_INVALID_DYN_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}
name += len;
}

- len = name - raw_command;
+ len = name - cmd;

- ret = check_command(raw_command + len);
+ ret = check_command(cmd + len);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- name = kmemdup_nul(raw_command + len, p - raw_command - len, GFP_KERNEL);
+ name = kmemdup_nul(cmd + len, p - cmd - len, GFP_KERNEL);
if (!name)
return -ENOMEM;

ret = __create_synth_event(name, fields);
-
kfree(name);
+ free:
+ kfree(cmd);

return ret;
}
--
2.17.1
\
 
 \ /
  Last update: 2020-12-17 22:17    [W:0.052 / U:0.028 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site