blob: 3f571b7fa86ad8c20ef40c23de74aee8c4f32583 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
*
* Updates:
* Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
*
*/
#include <trace-seq.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include "tracefs.h"
#include "tracefs-local.h"
#include "sqlhist-parse.h"
extern int yylex_init(void* ptr_yy_globals);
extern int yylex_init_extra(struct sqlhist_bison *sb, void* ptr_yy_globals);
extern int yylex_destroy (void * yyscanner );
struct str_hash {
struct str_hash *next;
char *str;
};
enum alias_type {
ALIAS_EVENT,
ALIAS_FIELD,
};
enum field_type {
FIELD_NONE,
FIELD_FROM,
FIELD_TO,
};
#define for_each_field(expr, field, table) \
for (expr = (table)->fields; expr; expr = (field)->next)
struct field {
struct expr *next; /* private link list */
const char *system;
const char *event_name;
struct tep_event *event;
const char *raw;
const char *label;
const char *field;
const char *type;
enum field_type ftype;
};
struct filter {
enum filter_type type;
struct expr *lval;
struct expr *rval;
};
struct match {
struct match *next;
struct expr *lval;
struct expr *rval;
};
struct compare {
enum compare_type type;
struct expr *lval;
struct expr *rval;
const char *name;
};
enum expr_type
{
EXPR_NUMBER,
EXPR_STRING,
EXPR_FIELD,
EXPR_FILTER,
EXPR_COMPARE,
};
struct expr {
struct expr *free_list;
struct expr *next;
enum expr_type type;
int line;
int idx;
union {
struct field field;
struct filter filter;
struct compare compare;
const char *string;
long number;
};
};
struct sql_table {
struct sqlhist_bison *sb;
const char *name;
struct expr *exprs;
struct expr *fields;
struct expr *from;
struct expr *to;
struct expr *where;
struct expr **next_where;
struct match *matches;
struct match **next_match;
struct expr *selections;
struct expr **next_selection;
};
__hidden int my_yyinput(void *extra, char *buf, int max)
{
struct sqlhist_bison *sb = extra;
if (!sb || !sb->buffer)
return -1;
if (sb->buffer_idx + max > sb->buffer_size)
max = sb->buffer_size - sb->buffer_idx;
if (max)
memcpy(buf, sb->buffer + sb->buffer_idx, max);
sb->buffer_idx += max;
return max;
}
__hidden void sql_parse_error(struct sqlhist_bison *sb, const char *text,
const char *fmt, va_list ap)
{
const char *buffer = sb->buffer;
struct trace_seq s;
int line = sb->line_no;
int idx = sb->line_idx - strlen(text);
int i;
if (!buffer)
return;
trace_seq_init(&s);
if (!s.buffer) {
tracefs_warning("Error allocating internal buffer\n");
return;
}
for (i = 0; line && buffer[i]; i++) {
if (buffer[i] == '\n')
line--;
}
for (; buffer[i] && buffer[i] != '\n'; i++)
trace_seq_putc(&s, buffer[i]);
trace_seq_putc(&s, '\n');
for (i = idx; i > 0; i--)
trace_seq_putc(&s, ' ');
trace_seq_puts(&s, "^\n");
trace_seq_printf(&s, "ERROR: '%s'\n", text);
trace_seq_vprintf(&s, fmt, ap);
trace_seq_terminate(&s);
sb->parse_error_str = strdup(s.buffer);
trace_seq_destroy(&s);
}
static void parse_error(struct sqlhist_bison *sb, const char *text,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
sql_parse_error(sb, text, fmt, ap);
va_end(ap);
}
__hidden unsigned int quick_hash(const char *str)
{
unsigned int val = 0;
int len = strlen(str);
for (; len >= 4; str += 4, len -= 4) {
val += str[0];
val += str[1] << 8;
val += str[2] << 16;
val += str[3] << 24;
}
for (; len > 0; str++, len--)
val += str[0] << (len * 8);
val *= 2654435761;
return val & ((1 << HASH_BITS) - 1);
}
static struct str_hash *find_string(struct sqlhist_bison *sb, const char *str)
{
unsigned int key = quick_hash(str);
struct str_hash *hash = sb->str_hash[key];
for (; hash; hash = hash->next) {
if (!strcmp(hash->str, str))
return hash;
}
return NULL;
}
/*
* If @str is found, then return the hash string.
* This lets store_str() know to free str.
*/
static char **add_hash(struct sqlhist_bison *sb, const char *str)
{
struct str_hash *hash;
unsigned int key;
if ((hash = find_string(sb, str))) {
return &hash->str;
}
hash = malloc(sizeof(*hash));
if (!hash)
return NULL;
key = quick_hash(str);
hash->next = sb->str_hash[key];
sb->str_hash[key] = hash;
hash->str = NULL;
return &hash->str;
}
__hidden char *store_str(struct sqlhist_bison *sb, const char *str)
{
char **pstr = add_hash(sb, str);
if (!pstr)
return NULL;
if (!(*pstr))
*pstr = strdup(str);
return *pstr;
}
__hidden void *add_cast(struct sqlhist_bison *sb,
void *data, const char *type)
{
struct expr *expr = data;
struct field *field = &expr->field;
field->type = type;
return expr;
}
__hidden int add_selection(struct sqlhist_bison *sb, void *select,
const char *name)
{
struct sql_table *table = sb->table;
struct expr *expr = select;
switch (expr->type) {
case EXPR_FIELD:
expr->field.label = name;
break;
case EXPR_COMPARE:
expr->compare.name = name;
break;
case EXPR_NUMBER:
case EXPR_STRING:
case EXPR_FILTER:
default:
return -1;
}
if (expr->next)
return -1;
*table->next_selection = expr;
table->next_selection = &expr->next;
return 0;
}
static struct expr *find_field(struct sqlhist_bison *sb,
const char *raw, const char *label)
{
struct field *field;
struct expr *expr;
for_each_field(expr, field, sb->table) {
field = &expr->field;
if (!strcmp(field->raw, raw)) {
if (label && !field->label)
field->label = label;
if (label && strcmp(label, field->label) != 0)
continue;
return expr;
}
if (label && !strcmp(field->raw, label)) {
if (!field->label) {
field->label = label;
field->raw = raw;
}
return expr;
}
if (!field->label)
continue;
if (!strcmp(field->label, raw))
return expr;
if (label && !strcmp(field->label, label))
return expr;
}
return NULL;
}
static void *create_expr(struct sqlhist_bison *sb,
enum expr_type type, struct expr **expr_p)
{
struct expr *expr;
expr = calloc(1, sizeof(*expr));
if (!expr)
return NULL;
if (expr_p)
*expr_p = expr;
expr->free_list = sb->table->exprs;
sb->table->exprs = expr;
expr->type = type;
expr->line = sb->line_no;
expr->idx = sb->line_idx;
switch (type) {
case EXPR_FIELD: return &expr->field;
case EXPR_COMPARE: return &expr->compare;
case EXPR_NUMBER: return &expr->number;
case EXPR_STRING: return &expr->string;
case EXPR_FILTER: return &expr->filter;
}
return NULL;
}
#define __create_expr(var, type, ENUM, expr) \
do { \
var = (type *)create_expr(sb, EXPR_##ENUM, expr); \
} while(0)
#define create_field(var, expr) \
__create_expr(var, struct field, FIELD, expr)
#define create_filter(var, expr) \
__create_expr(var, struct filter, FILTER, expr)
#define create_compare(var, expr) \
__create_expr(var, struct compare, COMPARE, expr)
#define create_string(var, expr) \
__create_expr(var, const char *, STRING, expr)
#define create_number(var, expr) \
__create_expr(var, long, NUMBER, expr)
__hidden void *add_field(struct sqlhist_bison *sb,
const char *field_name, const char *label)
{
struct sql_table *table = sb->table;
struct expr *expr;
struct field *field;
expr = find_field(sb, field_name, label);
if (expr)
return expr;
create_field(field, &expr);
field->next = table->fields;
table->fields = expr;
field->raw = field_name;
field->label = label;
return expr;
}
__hidden void *add_filter(struct sqlhist_bison *sb,
void *A, void *B, enum filter_type op)
{
struct filter *filter;
struct expr *expr;
create_filter(filter, &expr);
filter->lval = A;
filter->rval = B;
filter->type = op;
return expr;
}
__hidden int add_match(struct sqlhist_bison *sb, void *A, void *B)
{
struct sql_table *table = sb->table;
struct match *match;
match = calloc(1, sizeof(*match));
if (!match)
return -1;
match->lval = A;
match->rval = B;
*table->next_match = match;
table->next_match = &match->next;
return 0;
}
__hidden void *add_compare(struct sqlhist_bison *sb,
void *A, void *B, enum compare_type type)
{
struct compare *compare;
struct expr *expr;
create_compare(compare, &expr);
compare = &expr->compare;
compare->lval = A;
compare->rval = B;
compare->type = type;
return expr;
}
__hidden int add_where(struct sqlhist_bison *sb, void *item)
{
struct expr *expr = item;
struct sql_table *table = sb->table;
if (expr->type != EXPR_FILTER)
return -1;
*table->next_where = expr;
table->next_where = &expr->next;
if (expr->next)
return -1;
return 0;
}
__hidden int add_from(struct sqlhist_bison *sb, void *item)
{
struct expr *expr = item;
if (expr->type != EXPR_FIELD)
return -1;
sb->table->from = expr;
return 0;
}
__hidden int add_to(struct sqlhist_bison *sb, void *item)
{
struct expr *expr = item;
if (expr->type != EXPR_FIELD)
return -1;
sb->table->to = expr;
return 0;
}
__hidden void *add_string(struct sqlhist_bison *sb, const char *str)
{
struct expr *expr;
const char **str_p;
create_string(str_p, &expr);
*str_p = str;
return expr;
}
__hidden void *add_number(struct sqlhist_bison *sb, long val)
{
struct expr *expr;
long *num;
create_number(num, &expr);
*num = val;
return expr;
}
__hidden int table_start(struct sqlhist_bison *sb)
{
struct sql_table *table;
table = calloc(1, sizeof(*table));
if (!table)
return -ENOMEM;
table->sb = sb;
sb->table = table;
table->next_where = &table->where;
table->next_match = &table->matches;
table->next_selection = &table->selections;
return 0;
}
static int test_event_exists(struct tep_handle *tep,
struct sqlhist_bison *sb,
struct expr *expr, struct tep_event **pevent)
{
struct field *field = &expr->field;
const char *system = field->system;
const char *event = field->event_name;
if (!field->event)
field->event = tep_find_event_by_name(tep, system, event);
if (pevent)
*pevent = field->event;
if (field->event)
return 0;
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, field->raw, "event not found\n");
return -1;
}
static int test_field_exists(struct tep_handle *tep,
struct sqlhist_bison *sb,
struct expr *expr)
{
struct field *field = &expr->field;
struct tep_format_field *tfield;
char *field_name;
const char *p;
if (!field->event) {
if (test_event_exists(tep, sb, expr, NULL))
return -1;
}
/* The field could have a conversion */
p = strchr(field->field, '.');
if (p)
field_name = strndup(field->field, p - field->field);
else
field_name = strdup(field->field);
if (!field_name)
return -1;
if (!strcmp(field_name, TRACEFS_TIMESTAMP) ||
!strcmp(field->field, TRACEFS_TIMESTAMP_USECS))
tfield = (void *)1L;
else
tfield = tep_find_any_field(field->event, field_name);
free(field_name);
if (!tfield && (!strcmp(field->field, "COMM") || !strcmp(field->field, "comm")))
tfield = (void *)1L;
if (tfield)
return 0;
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, field->raw,
"Field '%s' not part of event %s\n",
field->field, field->event_name);
return -1;
}
static int update_vars(struct tep_handle *tep,
struct sql_table *table,
struct expr *expr)
{
struct sqlhist_bison *sb = table->sb;
struct field *event_field = &expr->field;
enum field_type ftype = FIELD_NONE;
struct tep_event *event;
struct field *field;
const char *label;
const char *raw = event_field->raw;
const char *event_name;
const char *system;
const char *p;
int label_len = 0, event_len, system_len;
if (expr == table->to)
ftype = FIELD_TO;
else if (expr == table->from)
ftype = FIELD_FROM;
p = strchr(raw, '.');
if (p) {
char *str;
str = strndup(raw, p - raw);
if (!str)
return -1;
event_field->system = store_str(sb, str);
free(str);
if (!event_field->system)
return -1;
p++;
} else {
p = raw;
}
event_field->event_name = store_str(sb, p);
if (!event_field->event_name)
return -1;
if (test_event_exists(tep, sb, expr, &event))
return -1;
if (!event_field->system)
event_field->system = store_str(sb, event->system);
if (!event_field->system)
return -1;
label = event_field->label;
if (label)
label_len = strlen(label);
system = event_field->system;
system_len = strlen(system);
event_name = event_field->event_name;
event_len = strlen(event_name);
for_each_field(expr, field, table) {
int len;
field = &expr->field;
if (field->event)
continue;
raw = field->raw;
/*
* The field could be:
* system.event.field...
* event.field...
* label.field...
* We check label first.
*/
len = label_len;
if (label && !strncmp(raw, label, len) &&
raw[len] == '.') {
/* Label matches and takes precedence */
goto found;
}
if (!strncmp(raw, system, system_len) &&
raw[system_len] == '.') {
raw += system_len + 1;
/* Check the event portion next */
}
len = event_len;
if (strncmp(raw, event_name, len) ||
raw[len] != '.') {
/* Does not match */
continue;
}
found:
field->system = system;
field->event_name = event_name;
field->event = event;
field->field = raw + len + 1;
field->ftype = ftype;
if (!strcmp(field->field, "TIMESTAMP"))
field->field = store_str(sb, TRACEFS_TIMESTAMP);
if (!strcmp(field->field, "TIMESTAMP_USECS"))
field->field = store_str(sb, TRACEFS_TIMESTAMP_USECS);
if (test_field_exists(tep, sb, expr))
return -1;
}
return 0;
}
/*
* Called when there's a FROM but no JOIN(to), which means that the
* selections can be fields and not mention the event itself.
*/
static int update_fields(struct tep_handle *tep,
struct sql_table *table,
struct expr *expr)
{
struct field *event_field = &expr->field;
struct sqlhist_bison *sb = table->sb;
struct tep_format_field *tfield;
struct tep_event *event;
struct field *field;
const char *p;
int len;
/* First update fields with aliases an such and add event */
update_vars(tep, table, expr);
/*
* If event is not found, the creation of the synth will
* add a proper error, so return "success".
*/
if (!event_field->event)
return 0;
event = event_field->event;
for_each_field(expr, field, table) {
const char *field_name;
field = &expr->field;
if (field->event)
continue;
field_name = field->raw;
p = strchr(field_name, '.');
if (p) {
len = p - field_name;
p = strndup(field_name, len);
if (!p)
return -1;
field_name = store_str(sb, p);
if (!field_name)
return -1;
free((char *)p);
}
tfield = tep_find_any_field(event, field_name);
/* Let it error properly later */
if (!tfield)
continue;
field->system = event_field->system;
field->event_name = event_field->event_name;
field->event = event;
field->field = field_name;
}
return 0;
}
static int match_error(struct sqlhist_bison *sb, struct match *match,
struct field *lmatch, struct field *rmatch)
{
struct field *lval = &match->lval->field;
struct field *rval = &match->rval->field;
struct field *field;
struct expr *expr;
if (lval->system != lmatch->system ||
lval->event != lmatch->event) {
expr = match->lval;
field = lval;
} else {
expr = match->rval;
field = rval;
}
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, field->raw,
"'%s' and '%s' must be a field for each event: '%s' and '%s'\n",
lval->raw, rval->raw, sb->table->to->field.raw,
sb->table->from->field.raw);
return -1;
}
static int test_match(struct sql_table *table, struct match *match)
{
struct field *lval, *rval;
struct field *to, *from;
if (!match->lval || !match->rval)
return -1;
if (match->lval->type != EXPR_FIELD || match->rval->type != EXPR_FIELD)
return -1;
to = &table->to->field;
from = &table->from->field;
lval = &match->lval->field;
rval = &match->rval->field;
/*
* Note, strings are stored in the string store, so all
* duplicate strings are the same value, and we can use
* normal "==" and "!=" instead of strcmp().
*
* Either lval == to and rval == from
* or lval == from and rval == to.
*/
if ((lval->system != to->system) ||
(lval->event != to->event)) {
if ((rval->system != to->system) ||
(rval->event != to->event) ||
(lval->system != from->system) ||
(lval->event != from->event))
return match_error(table->sb, match, from, to);
} else {
if ((rval->system != from->system) ||
(rval->event != from->event) ||
(lval->system != to->system) ||
(lval->event != to->event))
return match_error(table->sb, match, to, from);
}
return 0;
}
static void assign_match(const char *system, const char *event,
struct match *match,
const char **start_match, const char **end_match)
{
struct field *lval, *rval;
lval = &match->lval->field;
rval = &match->rval->field;
if (lval->system == system &&
lval->event_name == event) {
*start_match = lval->field;
*end_match = rval->field;
} else {
*start_match = rval->field;
*end_match = lval->field;
}
}
static int build_compare(struct tracefs_synth *synth,
const char *system, const char *event,
struct compare *compare)
{
const char *start_field;
const char *end_field;
struct field *lval, *rval;
enum tracefs_synth_calc calc;
int ret;
if (!compare->name)
return -1;
lval = &compare->lval->field;
rval = &compare->rval->field;
if (lval->system == system &&
lval->event_name == event) {
start_field = lval->field;
end_field = rval->field;
calc = TRACEFS_SYNTH_DELTA_START;
} else {
start_field = rval->field;
end_field = lval->field;
calc = TRACEFS_SYNTH_DELTA_END;
}
if (compare->type == COMPARE_ADD)
calc = TRACEFS_SYNTH_ADD;
ret = tracefs_synth_add_compare_field(synth, start_field,
end_field, calc,
compare->name);
return ret;
}
static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr,
const char *event)
{
struct field *field = &expr->field;
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, field->raw,
"event '%s' can not be grouped or '||' together with '%s'\n"
"All filters between '&&' must be for the same event\n",
field->event, event);
return -1;
}
static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter,
const char **system, const char **event,
enum field_type *ftype)
{
int ret;
if (filter->type == FILTER_OR ||
filter->type == FILTER_AND) {
ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
if (ret)
return ret;
return do_verify_filter(sb, &filter->rval->filter, system, event, ftype);
}
if (filter->type == FILTER_GROUP ||
filter->type == FILTER_NOT_GROUP) {
return do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
}
/*
* system and event will be NULL until we find the left most
* node. Then assign it, and compare on the way back up.
*/
if (!*system && !*event) {
*system = filter->lval->field.system;
*event = filter->lval->field.event_name;
*ftype = filter->lval->field.ftype;
return 0;
}
if (filter->lval->field.system != *system ||
filter->lval->field.event_name != *event)
return verify_filter_error(sb, filter->lval, *event);
return 0;
}
static int verify_filter(struct sqlhist_bison *sb, struct filter *filter,
const char **system, const char **event,
enum field_type *ftype)
{
int ret;
switch (filter->type) {
case FILTER_OR:
case FILTER_AND:
case FILTER_GROUP:
case FILTER_NOT_GROUP:
break;
default:
return do_verify_filter(sb, filter, system, event, ftype);
}
ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
if (ret)
return ret;
switch (filter->type) {
case FILTER_OR:
case FILTER_AND:
return do_verify_filter(sb, &filter->rval->filter, system, event, ftype);
default:
return 0;
}
}
static int test_field_exists(struct tep_handle *tep, struct sqlhist_bison *sb,
struct expr *expr);
static void filter_compare_error(struct tep_handle *tep,
struct sqlhist_bison *sb,
struct expr *expr)
{
struct field *field = &expr->field;
switch (errno) {
case ENODEV:
case EBADE:
break;
case EINVAL:
parse_error(sb, field->raw, "Invalid compare\n");
break;
default:
parse_error(sb, field->raw, "System error?\n");
return;
}
/* ENODEV means that an event or field does not exist */
if (errno == ENODEV) {
if (test_field_exists(tep, sb, expr))
return;
if (test_field_exists(tep, sb, expr))
return;
return;
}
/* fields exist, but values are not compatible */
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, field->raw,
"Field '%s' is not compatible to be compared with the given value\n",
field->field);
}
static void filter_error(struct tep_handle *tep,
struct sqlhist_bison *sb, struct expr *expr)
{
struct filter *filter = &expr->filter;
sb->line_no = expr->line;
sb->line_idx = expr->idx;
switch (filter->type) {
case FILTER_NOT_GROUP:
case FILTER_GROUP:
case FILTER_OR:
case FILTER_AND:
break;
default:
filter_compare_error(tep, sb, filter->lval);
return;
}
sb->line_no = expr->line;
sb->line_idx = expr->idx;
parse_error(sb, "", "Problem with filter entry?\n");
}
static int build_filter(struct tep_handle *tep, struct sqlhist_bison *sb,
struct tracefs_synth *synth,
bool start, struct expr *expr, bool *started)
{
int (*append_filter)(struct tracefs_synth *synth,
enum tracefs_filter type,
const char *field,
enum tracefs_compare compare,
const char *val);
struct filter *filter = &expr->filter;
enum tracefs_compare cmp;
const char *val;
int and_or = TRACEFS_FILTER_AND;
char num[64];
int ret;
if (start)
append_filter = tracefs_synth_append_start_filter;
else
append_filter = tracefs_synth_append_end_filter;
if (started && *started) {
ret = append_filter(synth, and_or, NULL, 0, NULL);
ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN,
NULL, 0, NULL);
}
switch (filter->type) {
case FILTER_NOT_GROUP:
ret = append_filter(synth, TRACEFS_FILTER_NOT,
NULL, 0, NULL);
if (ret < 0)
goto out;
/* Fall through */
case FILTER_GROUP:
ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN,
NULL, 0, NULL);
if (ret < 0)
goto out;
ret = build_filter(tep, sb, synth, start, filter->lval, NULL);
if (ret < 0)
goto out;
ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN,
NULL, 0, NULL);
goto out;
case FILTER_OR:
and_or = TRACEFS_FILTER_OR;
/* Fall through */
case FILTER_AND:
ret = build_filter(tep, sb, synth, start, filter->lval, NULL);
if (ret < 0)
goto out;
ret = append_filter(synth, and_or, NULL, 0, NULL);
if (ret)
goto out;
ret = build_filter(tep, sb, synth, start, filter->rval, NULL);
goto out;
default:
break;
}
switch (filter->rval->type) {
case EXPR_NUMBER:
sprintf(num, "%ld", filter->rval->number);
val = num;
break;
case EXPR_STRING:
val = filter->rval->string;
break;
default:
break;
}
switch (filter->type) {
case FILTER_EQ: cmp = TRACEFS_COMPARE_EQ; break;
case FILTER_NE: cmp = TRACEFS_COMPARE_NE; break;
case FILTER_LE: cmp = TRACEFS_COMPARE_LE; break;
case FILTER_LT: cmp = TRACEFS_COMPARE_LT; break;
case FILTER_GE: cmp = TRACEFS_COMPARE_GE; break;
case FILTER_GT: cmp = TRACEFS_COMPARE_GT; break;
case FILTER_BIN_AND: cmp = TRACEFS_COMPARE_AND; break;
case FILTER_STR_CMP: cmp = TRACEFS_COMPARE_RE; break;
default:
tracefs_warning("Error invalid filter type '%d'", filter->type);
return ERANGE;
}
ret = append_filter(synth, TRACEFS_FILTER_COMPARE,
filter->lval->field.field, cmp, val);
if (ret)
filter_error(tep, sb, expr);
out:
if (!ret && started) {
if (*started)
ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN,
NULL, 0, NULL);
*started = true;
}
return ret;
}
static void *field_match_error(struct tep_handle *tep, struct sqlhist_bison *sb,
struct match *match)
{
switch (errno) {
case ENODEV:
case EBADE:
break;
default:
/* System error */
return NULL;
}
/* ENODEV means that an event or field does not exist */
if (errno == ENODEV) {
if (test_field_exists(tep, sb, match->lval))
return NULL;
if (test_field_exists(tep, sb, match->rval))
return NULL;
return NULL;
}
/* fields exist, but values are not compatible */
sb->line_no = match->lval->line;
sb->line_idx = match->lval->idx;
parse_error(sb, match->lval->field.raw,
"Field '%s' is not compatible to match field '%s'\n",
match->lval->field.raw, match->rval->field.raw);
return NULL;
}
static void *synth_init_error(struct tep_handle *tep, struct sql_table *table)
{
struct sqlhist_bison *sb = table->sb;
struct match *match = table->matches;
switch (errno) {
case ENODEV:
case EBADE:
break;
default:
/* System error */
return NULL;
}
/* ENODEV could mean that start or end events do not exist */
if (errno == ENODEV) {
if (test_event_exists(tep, sb, table->from, NULL))
return NULL;
if (test_event_exists(tep, sb, table->to, NULL))
return NULL;
}
return field_match_error(tep, sb, match);
}
static void selection_error(struct tep_handle *tep,
struct sqlhist_bison *sb, struct expr *expr)
{
/* We just care about event not existing */
if (errno != ENODEV)
return;
test_field_exists(tep, sb, expr);
}
static void compare_error(struct tep_handle *tep,
struct sqlhist_bison *sb, struct expr *expr)
{
struct compare *compare = &expr->compare;
if (!compare->name) {
sb->line_no = expr->line;
sb->line_idx = expr->idx + strlen("no name");
parse_error(sb, "no name",
"Field calculations must be labeled 'AS name'\n");
}
switch (errno) {
case ENODEV:
case EBADE:
break;
default:
/* System error */
return;
}
/* ENODEV means that an event or field does not exist */
if (errno == ENODEV) {
if (test_field_exists(tep, sb, compare->lval))
return;
if (test_field_exists(tep, sb, compare->rval))
return;
return;
}
/* fields exist, but values are not compatible */
sb->line_no = compare->lval->line;
sb->line_idx = compare->lval->idx;
parse_error(sb, compare->lval->field.raw,
"'%s' is not compatible to compare with '%s'\n",
compare->lval->field.raw, compare->rval->field.raw);
}
static void compare_no_to_error(struct sqlhist_bison *sb, struct expr *expr)
{
struct compare *compare = &expr->compare;
sb->line_no = compare->lval->line;
sb->line_idx = compare->lval->idx;
parse_error(sb, compare->lval->field.raw,
"Simple SQL (without JOIN/ON) do not allow comparisons\n",
compare->lval->field.raw, compare->rval->field.raw);
}
static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr,
const char *from_event, const char *event)
{
while (expr) {
switch (expr->filter.type) {
case FILTER_OR:
case FILTER_AND:
case FILTER_GROUP:
case FILTER_NOT_GROUP:
expr = expr->filter.lval;
continue;
default:
break;
}
break;
}
sb->line_no = expr->filter.lval->line;
sb->line_idx = expr->filter.lval->idx;
parse_error(sb, expr->filter.lval->field.raw,
"Event '%s' does not match FROM event '%s'\n",
event, from_event);
}
static int verify_field_type(struct tep_handle *tep,
struct sqlhist_bison *sb,
struct expr *expr, int *cnt)
{
struct field *field = &expr->field;
struct tep_event *event;
struct tep_format_field *tfield;
char *type;
int ret;
int i;
if (!field->type)
return 0;
sb->line_no = expr->line;
sb->line_idx = expr->idx;
event = tep_find_event_by_name(tep, field->system, field->event_name);
if (!event) {
parse_error(sb, field->raw,
"Event '%s' not found\n",
field->event_name ? : "(null)");
return -1;
}
tfield = tep_find_any_field(event, field->field);
if (!tfield) {
parse_error(sb, field->raw,
"Field '%s' not part of event '%s'\n",
field->field ? : "(null)", field->event);
return -1;
}
type = strdup(field->type);
if (!type)
return -1;
if (!strcmp(type, TRACEFS_HIST_COUNTER) ||
!strcmp(type, "_COUNTER_")) {
ret = HIST_COUNTER_TYPE;
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) {
parse_error(sb, field->raw,
"'%s' is a string, and counters may only be used with numbers\n");
ret = -1;
}
goto out;
}
for (i = 0; type[i]; i++)
type[i] = tolower(type[i]);
if (!strcmp(type, "hex")) {
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_HEX;
} else if (!strcmp(type, "sym")) {
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_SYM;
} else if (!strcmp(type, "sym-offset")) {
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_SYM_OFFSET;
} else if (!strcmp(type, "syscall")) {
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_SYSCALL;
} else if (!strcmp(type, "execname") ||
!strcmp(type, "comm")) {
ret = TRACEFS_HIST_KEY_EXECNAME;
if (strcmp(field->field, "common_pid")) {
parse_error(sb, field->raw,
"'%s' is only allowed for common_pid\n",
type);
ret = -1;
}
} else if (!strcmp(type, "log") ||
!strcmp(type, "log2")) {
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_LOG;
} else if (!strncmp(type, "buckets", 7)) {
if (type[7] != '=' || !isdigit(type[8])) {
parse_error(sb, field->raw,
"buckets type must have '=[number]' after it\n");
ret = -1;
goto out;
}
*cnt = atoi(&type[8]);
if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
goto fail_type;
ret = TRACEFS_HIST_KEY_BUCKETS;
} else {
parse_error(sb, field->raw,
"Cast of '%s' to unknown type '%s'\n",
field->raw, type);
ret = -1;
}
out:
free(type);
return ret;
fail_type:
parse_error(sb, field->raw,
"Field '%s' cast to '%s' but is of type %s\n",
field->field, type, tfield->flags & TEP_FIELD_IS_STRING ?
"string" : "array");
free(type);
return -1;
}
static struct tracefs_synth *build_synth(struct tep_handle *tep,
const char *name,
struct sql_table *table)
{
struct tracefs_synth *synth;
struct field *field;
struct match *match;
struct expr *expr;
const char *start_system;
const char *start_event;
const char *end_system;
const char *end_event;
const char *start_match;
const char *end_match;
bool started_start = false;
bool started_end = false;
bool non_val = false;
int ret;
if (!table->from)
return NULL;
/* This could be a simple SQL statement to only build a histogram */
if (!table->to) {
ret = update_fields(tep, table, table->from);
if (ret < 0)
return NULL;
start_system = table->from->field.system;
start_event = table->from->field.event_name;
synth = synth_init_from(tep, start_system, start_event);
if (!synth)
return synth_init_error(tep, table);
goto hist_only;
}
ret = update_vars(tep, table, table->from);
if (ret < 0)
return NULL;
ret = update_vars(tep, table, table->to);
if (ret < 0)
return NULL;
start_system = table->from->field.system;
start_event = table->from->field.event_name;
match = table->matches;
if (!match)
return NULL;
ret = test_match(table, match);
if (ret < 0)
return NULL;
end_system = table->to->field.system;
end_event = table->to->field.event_name;
assign_match(start_system, start_event, match,
&start_match, &end_match);
synth = tracefs_synth_alloc(tep, name, start_system,
start_event, end_system, end_event,
start_match, end_match, NULL);
if (!synth)
return synth_init_error(tep, table);
for (match = match->next; match; match = match->next) {
ret = test_match(table, match);
if (ret < 0)
goto free;
assign_match(start_system, start_event, match,
&start_match, &end_match);
ret = tracefs_synth_add_match_field(synth,
start_match,
end_match, NULL);
if (ret < 0) {
field_match_error(tep, table->sb, match);
goto free;
}
}
hist_only:
/* table->to may be NULL here */
for (expr = table->selections; expr; expr = expr->next) {
if (expr->type == EXPR_FIELD) {
ret = -1;
field = &expr->field;
if (field->ftype != FIELD_TO &&
field->system == start_system &&
field->event_name == start_event) {
int type;
int cnt = 0;
type = verify_field_type(tep, table->sb, expr, &cnt);
if (type < 0)
goto free;
if (type != HIST_COUNTER_TYPE)
non_val = true;
ret = synth_add_start_field(synth,
field->field, field->label,
type, cnt);
} else if (table->to) {
ret = tracefs_synth_add_end_field(synth,
field->field, field->label);
}
if (ret < 0) {
selection_error(tep, table->sb, expr);
goto free;
}
continue;
}
if (!table->to) {
compare_no_to_error(table->sb, expr);
goto free;
}
if (expr->type != EXPR_COMPARE)
goto free;
ret = build_compare(synth, start_system, end_system,
&expr->compare);
if (ret < 0) {
compare_error(tep, table->sb, expr);
goto free;
}
}
if (!non_val && !table->to) {
table->sb->line_no = 0;
table->sb->line_idx = 10;
parse_error(table->sb, "CAST",
"Not all SELECT items can be of type _COUNTER_\n");
goto free;
}
for (expr = table->where; expr; expr = expr->next) {
const char *filter_system = NULL;
const char *filter_event = NULL;
enum field_type ftype = FIELD_NONE;
bool *started;
bool start;
ret = verify_filter(table->sb, &expr->filter, &filter_system,
&filter_event, &ftype);
if (ret < 0)
goto free;
start = filter_system == start_system &&
filter_event == start_event &&
ftype != FIELD_TO;
if (start)
started = &started_start;
else if (!table->to) {
where_no_to_error(table->sb, expr, start_event,
filter_event);
goto free;
} else
started = &started_end;
ret = build_filter(tep, table->sb, synth, start, expr, started);
if (ret < 0)
goto free;
}
return synth;
free:
tracefs_synth_free(synth);
return NULL;
}
static void free_sql_table(struct sql_table *table)
{
struct match *match;
struct expr *expr;
if (!table)
return;
while ((expr = table->exprs)) {
table->exprs = expr->free_list;
free(expr);
}
while ((match = table->matches)) {
table->matches = match->next;
free(match);
}
free(table);
}
static void free_str_hash(struct str_hash **hash)
{
struct str_hash *item;
int i;
for (i = 0; i < 1 << HASH_BITS; i++) {
while ((item = hash[i])) {
hash[i] = item->next;
free(item->str);
free(item);
}
}
}
static void free_sb(struct sqlhist_bison *sb)
{
free_sql_table(sb->table);
free_str_hash(sb->str_hash);
free(sb->parse_error_str);
}
struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name,
const char *sql_buffer, char **err)
{
struct tracefs_synth *synth = NULL;
struct sqlhist_bison sb;
int ret;
if (!tep || !sql_buffer) {
errno = EINVAL;
return NULL;
}
memset(&sb, 0, sizeof(sb));
sb.buffer = sql_buffer;
sb.buffer_size = strlen(sql_buffer);
sb.buffer_idx = 0;
ret = yylex_init_extra(&sb, &sb.scanner);
if (ret < 0) {
yylex_destroy(sb.scanner);
return NULL;
}
ret = tracefs_parse(&sb);
yylex_destroy(sb.scanner);
if (ret)
goto free;
synth = build_synth(tep, name, sb.table);
free:
if (!synth) {
if (sb.parse_error_str && err) {
*err = sb.parse_error_str;
sb.parse_error_str = NULL;
}
}
free_sb(&sb);
return synth;
}