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 <zanu...@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

Reply via email to