You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1407 lines
33 KiB
1407 lines
33 KiB
/* |
|
* lsfd-filter.c - filtering engine for lsfd |
|
* |
|
* Copyright (C) 2021 Red Hat, Inc. |
|
* Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com> |
|
* |
|
* This file may be redistributed under the terms of the |
|
* GNU Lesser General Public License. |
|
*/ |
|
|
|
#include "lsfd-filter.h" |
|
|
|
#include "nls.h" |
|
#include "strutils.h" |
|
#include "xalloc.h" |
|
|
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <regex.h> /* regcomp(), regexec() */ |
|
|
|
/* |
|
* Definitions |
|
*/ |
|
#define COL_HEADER_EXTRA_CHARS ":-_%" /* ??? */ |
|
#define GOT_ERROR(PARSERorFILTER)(*((PARSERorFILTER)->errmsg)) |
|
|
|
/* |
|
* Types |
|
*/ |
|
|
|
enum token_type { |
|
TOKEN_NAME, /* [A-Za-z_][-_:%A-Za-z0-9]* */ |
|
TOKEN_STR, /* "...", '...' */ |
|
TOKEN_DEC, /* [1-9][0-9]+, NOTE: negative value is no dealt. */ |
|
TOKEN_HEX, /* 0x[0-9a-f]+ not implemented */ |
|
TOKEN_OCT, /* 0[1-7]+ not implemented */ |
|
TOKEN_TRUE, /* true */ |
|
TOKEN_FALSE, /* false */ |
|
TOKEN_OPEN, /* ( */ |
|
TOKEN_CLOSE, /* ) */ |
|
TOKEN_OP1, /* !, not */ |
|
TOKEN_OP2, /* TODO: =*, !* (glob match with fnmatch() */ |
|
TOKEN_EOF, |
|
}; |
|
|
|
enum op1_type { |
|
OP1_NOT, |
|
}; |
|
|
|
enum op2_type { |
|
OP2_EQ, |
|
OP2_NE, |
|
OP2_AND, |
|
OP2_OR, |
|
OP2_LT, |
|
OP2_LE, |
|
OP2_GT, |
|
OP2_GE, |
|
OP2_RE_MATCH, |
|
OP2_RE_UNMATCH, |
|
}; |
|
|
|
struct token { |
|
enum token_type type; |
|
union { |
|
char *str; |
|
unsigned long long num; |
|
enum op1_type op1; |
|
enum op2_type op2; |
|
} val; |
|
}; |
|
|
|
struct token_class { |
|
const char *name; |
|
void (*free)(struct token *); |
|
void (*dump)(struct token *, FILE *); |
|
}; |
|
|
|
struct parameter { |
|
struct libscols_column *cl; |
|
bool has_value; |
|
union { |
|
const char *str; |
|
unsigned long long num; |
|
bool boolean; |
|
} val; |
|
}; |
|
|
|
struct parser { |
|
const char *expr; |
|
const char *cursor; |
|
int paren_level; |
|
struct libscols_table *tb; |
|
int (*column_name_to_id)(const char *, void *); |
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*); |
|
void *data; |
|
struct parameter *parameters; |
|
#define ERRMSG_LEN 128 |
|
char errmsg[ERRMSG_LEN]; |
|
}; |
|
|
|
enum node_type { |
|
NODE_STR, |
|
NODE_NUM, |
|
NODE_BOOL, |
|
NODE_RE, |
|
NODE_OP1, |
|
NODE_OP2, |
|
}; |
|
|
|
struct node { |
|
enum node_type type; |
|
}; |
|
|
|
struct op1_class { |
|
const char *name; |
|
/* Return true if acceptable. */ |
|
bool (*is_acceptable)(struct node *, struct parameter *, struct libscols_line *); |
|
/* Return true if o.k. */ |
|
bool (*check_type)(struct parser *, struct op1_class *, struct node *); |
|
}; |
|
|
|
struct op2_class { |
|
const char *name; |
|
/* Return true if acceptable. */ |
|
bool (*is_acceptable)(struct node *, struct node *, struct parameter *, struct libscols_line *); |
|
/* Return true if o.k. */ |
|
bool (*check_type)(struct parser *, struct op2_class *, struct node *, struct node *); |
|
}; |
|
|
|
#define VAL(NODE,FIELD) (((struct node_val *)(NODE))->val.FIELD) |
|
#define PINDEX(NODE) (((struct node_val *)(NODE))->pindex) |
|
struct node_val { |
|
struct node base; |
|
int pindex; |
|
union { |
|
char *str; |
|
unsigned long long num; |
|
bool boolean; |
|
regex_t re; |
|
} val; |
|
}; |
|
|
|
struct node_op1 { |
|
struct node base; |
|
struct op1_class *opclass; |
|
struct node *arg; |
|
}; |
|
|
|
struct node_op2 { |
|
struct node base; |
|
struct op2_class *opclass; |
|
struct node *args[2]; |
|
}; |
|
|
|
struct node_class { |
|
const char *name; |
|
void (*free)(struct node *); |
|
void (*dump)(struct node *, struct parameter*, int, FILE *); |
|
}; |
|
|
|
struct lsfd_filter { |
|
struct libscols_table *table; |
|
struct node *node; |
|
struct parameter *parameters; |
|
int nparams; |
|
char errmsg[ERRMSG_LEN]; |
|
}; |
|
|
|
/* |
|
* Prototypes |
|
*/ |
|
static struct node *node_val_new(enum node_type, int pindex); |
|
static void node_free (struct node *); |
|
static bool node_apply(struct node *, struct parameter *, struct libscols_line *); |
|
static void node_dump (struct node *, struct parameter *, int, FILE *); |
|
|
|
static struct token *token_new (void); |
|
static void token_free(struct token *); |
|
#ifdef DEBUG |
|
static void token_dump(struct token *, FILE *); |
|
#endif /* DEBUG */ |
|
|
|
static void token_free_str(struct token *); |
|
|
|
static void token_dump_str(struct token *, FILE *); |
|
static void token_dump_num(struct token *, FILE *); |
|
static void token_dump_op1(struct token *, FILE *); |
|
static void token_dump_op2(struct token *, FILE *); |
|
|
|
static bool op1_not(struct node *, struct parameter*, struct libscols_line *); |
|
static bool op1_check_type_bool_or_op(struct parser *, struct op1_class *, struct node *); |
|
|
|
static bool op2_eq (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_ne (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_and(struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_or (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_lt (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_le (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_gt (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_ge (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_re_match (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
static bool op2_re_unmatch (struct node *, struct node *, struct parameter*, struct libscols_line *); |
|
|
|
static bool op2_check_type_eq_or_bool_or_op(struct parser *, struct op2_class *, struct node *, struct node *); |
|
static bool op2_check_type_boolean_or_op (struct parser *, struct op2_class *, struct node *, struct node *); |
|
static bool op2_check_type_num (struct parser *, struct op2_class *, struct node *, struct node *); |
|
static bool op2_check_type_re (struct parser *, struct op2_class *, struct node *, struct node *); |
|
|
|
static void node_str_free(struct node *); |
|
static void node_re_free (struct node *); |
|
static void node_op1_free(struct node *); |
|
static void node_op2_free(struct node *); |
|
|
|
static void node_str_dump (struct node *, struct parameter*, int, FILE *); |
|
static void node_num_dump (struct node *, struct parameter*, int, FILE *); |
|
static void node_bool_dump(struct node *, struct parameter*, int, FILE *); |
|
static void node_re_dump (struct node *, struct parameter*, int, FILE *); |
|
static void node_op1_dump (struct node *, struct parameter*, int, FILE *); |
|
static void node_op2_dump (struct node *, struct parameter*, int, FILE *); |
|
|
|
static struct node *dparser_compile(struct parser *); |
|
|
|
/* |
|
* Data |
|
*/ |
|
#define TOKEN_CLASS(TOKEN) (&token_classes[(TOKEN)->type]) |
|
static struct token_class token_classes [] = { |
|
[TOKEN_NAME] = { |
|
.name = "NAME", |
|
.free = token_free_str, |
|
.dump = token_dump_str, |
|
}, |
|
[TOKEN_STR] = { |
|
.name = "STR", |
|
.free = token_free_str, |
|
.dump = token_dump_str, |
|
}, |
|
[TOKEN_DEC] = { |
|
.name = "DEC", |
|
.dump = token_dump_num, |
|
}, |
|
[TOKEN_TRUE] = { |
|
.name = "true", |
|
}, |
|
[TOKEN_FALSE] = { |
|
.name = "false", |
|
}, |
|
[TOKEN_OPEN] = { |
|
.name = "OPEN", |
|
}, |
|
[TOKEN_CLOSE] = { |
|
.name = "CLOSE", |
|
}, |
|
[TOKEN_OP1] = { |
|
.name = "OP1", |
|
.dump = token_dump_op1, |
|
}, |
|
[TOKEN_OP2] = { |
|
.name = "OP2", |
|
.dump = token_dump_op2, |
|
}, |
|
[TOKEN_EOF] = { |
|
.name = "TOKEN_EOF", |
|
}, |
|
}; |
|
|
|
#define TOKEN_OP1_CLASS(TOKEN) (&(op1_classes[(TOKEN)->val.op1])) |
|
static struct op1_class op1_classes [] = { |
|
[OP1_NOT] = { |
|
.name = "!", |
|
.is_acceptable = op1_not, |
|
.check_type = op1_check_type_bool_or_op, |
|
}, |
|
}; |
|
|
|
#define TOKEN_OP2_CLASS(TOKEN) (&(op2_classes[(TOKEN)->val.op2])) |
|
static struct op2_class op2_classes [] = { |
|
[OP2_EQ] = { |
|
.name = "==", |
|
.is_acceptable = op2_eq, |
|
.check_type = op2_check_type_eq_or_bool_or_op |
|
}, |
|
[OP2_NE] = { |
|
.name = "!=", |
|
.is_acceptable = op2_ne, |
|
.check_type = op2_check_type_eq_or_bool_or_op, |
|
}, |
|
[OP2_AND] = { |
|
.name = "&&", |
|
.is_acceptable = op2_and, |
|
.check_type = op2_check_type_boolean_or_op, |
|
}, |
|
[OP2_OR] = { |
|
.name = "||", |
|
.is_acceptable = op2_or, |
|
.check_type = op2_check_type_boolean_or_op, |
|
}, |
|
[OP2_LT] = { |
|
.name = "<", |
|
.is_acceptable = op2_lt, |
|
.check_type = op2_check_type_num, |
|
}, |
|
[OP2_LE] = { |
|
.name = "<=", |
|
.is_acceptable = op2_le, |
|
.check_type = op2_check_type_num, |
|
}, |
|
[OP2_GT] = { |
|
.name = ">", |
|
.is_acceptable = op2_gt, |
|
.check_type = op2_check_type_num, |
|
}, |
|
[OP2_GE] = { |
|
.name = ">=", |
|
.is_acceptable = op2_ge, |
|
.check_type = op2_check_type_num, |
|
}, |
|
[OP2_RE_MATCH] = { |
|
.name = "=~", |
|
.is_acceptable = op2_re_match, |
|
.check_type = op2_check_type_re, |
|
}, |
|
[OP2_RE_UNMATCH] = { |
|
.name = "!~", |
|
.is_acceptable = op2_re_unmatch, |
|
.check_type = op2_check_type_re, |
|
}, |
|
}; |
|
|
|
#define NODE_CLASS(NODE) (&node_classes[(NODE)->type]) |
|
static struct node_class node_classes[] = { |
|
[NODE_STR] = { |
|
.name = "STR", |
|
.free = node_str_free, |
|
.dump = node_str_dump, |
|
}, |
|
[NODE_NUM] = { |
|
.name = "NUM", |
|
.dump = node_num_dump, |
|
}, |
|
[NODE_BOOL] = { |
|
.name = "BOOL", |
|
.dump = node_bool_dump, |
|
}, |
|
[NODE_RE] = { |
|
.name = "STR", |
|
.free = node_re_free, |
|
.dump = node_re_dump, |
|
}, |
|
[NODE_OP1] = { |
|
.name = "OP1", |
|
.free = node_op1_free, |
|
.dump = node_op1_dump, |
|
}, |
|
[NODE_OP2] = { |
|
.name = "OP2", |
|
.free = node_op2_free, |
|
.dump = node_op2_dump, |
|
} |
|
}; |
|
|
|
/* |
|
* Functions |
|
*/ |
|
static int strputc(char **a, const char b) |
|
{ |
|
return strappend(a, (char [2]){b, '\0'}); |
|
} |
|
|
|
static void xstrputc(char **a, const char b) |
|
{ |
|
int rc = strputc(a, b); |
|
if (rc < 0) |
|
errx(EXIT_FAILURE, _("failed to allocate memory")); |
|
} |
|
|
|
static void parser_init(struct parser *parser, const char *const expr, struct libscols_table *tb, |
|
int ncols, |
|
int (*column_name_to_id)(const char *, void *), |
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*), |
|
void *data) |
|
{ |
|
parser->expr = expr; |
|
parser->cursor = parser->expr; |
|
parser->paren_level = 0; |
|
parser->tb = tb; |
|
parser->column_name_to_id = column_name_to_id; |
|
parser->add_column_by_id = add_column_by_id; |
|
parser->data = data; |
|
parser->parameters = xcalloc(ncols, sizeof(struct parameter)); |
|
parser->errmsg[0] = '\0'; |
|
} |
|
|
|
static char parser_getc(struct parser *parser) |
|
{ |
|
char c = *parser->cursor; |
|
if (c != '\0') |
|
parser->cursor++; |
|
return c; |
|
} |
|
|
|
static void parser_ungetc(struct parser *parser, char c) |
|
{ |
|
assert(parser->cursor > parser->expr); |
|
if (c != '\0') |
|
parser->cursor--; |
|
} |
|
|
|
static void parser_read_str(struct parser *parser, struct token *token, char delimiter) |
|
{ |
|
bool escape = false; |
|
while (1) { |
|
char c = parser_getc(parser); |
|
|
|
if (c == '\0') { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: string literal is not terminated: %s"), |
|
token->val.str? : ""); |
|
return; |
|
} else if (escape) { |
|
switch (c) { |
|
case '\\': |
|
case '\'': |
|
case '"': |
|
xstrputc(&token->val.str, c); |
|
break; |
|
case 'n': |
|
xstrputc(&token->val.str, '\n'); |
|
break; |
|
case 't': |
|
xstrputc(&token->val.str, '\t'); |
|
break; |
|
/* TODO: \f, \r, ... */ |
|
default: |
|
xstrputc(&token->val.str, '\\'); |
|
xstrputc(&token->val.str, c); |
|
return; |
|
} |
|
escape = false; |
|
} else if (c == delimiter) { |
|
if (token->val.str == NULL) |
|
token->val.str = xstrdup(""); |
|
return; |
|
} else if (c == '\\') |
|
escape = true; |
|
else |
|
xstrputc(&token->val.str, c); |
|
} |
|
} |
|
|
|
static void parser_read_name(struct parser *parser, struct token *token) |
|
{ |
|
while (1) { |
|
char c = parser_getc(parser); |
|
if (c == '\0') |
|
break; |
|
if (strchr(COL_HEADER_EXTRA_CHARS, c) || isalnum((unsigned char)c)) { |
|
xstrputc(&token->val.str, c); |
|
continue; |
|
} |
|
parser_ungetc(parser, c); |
|
break; |
|
} |
|
} |
|
|
|
static int parser_read_dec(struct parser *parser, struct token *token) |
|
{ |
|
int rc = 0; |
|
while (1) { |
|
char c = parser_getc(parser); |
|
if (c == '\0') |
|
break; |
|
if (isdigit((unsigned char)c)) { |
|
xstrputc(&token->val.str, c); |
|
continue; |
|
} |
|
parser_ungetc(parser, c); |
|
break; |
|
} |
|
|
|
errno = 0; |
|
unsigned long long num = strtoull(token->val.str, NULL, 10); |
|
rc = errno; |
|
free(token->val.str); |
|
token->val.num = num; |
|
return rc; |
|
} |
|
|
|
static struct token *parser_read(struct parser *parser) |
|
{ |
|
struct token *t = token_new(); |
|
char c, c0; |
|
|
|
do |
|
c = parser_getc(parser); |
|
while (isspace((unsigned char)c)); |
|
|
|
switch (c) { |
|
case '\0': |
|
t->type = TOKEN_EOF; |
|
break; |
|
case '(': |
|
t->type = TOKEN_OPEN; |
|
parser->paren_level++; |
|
break; |
|
case ')': |
|
t->type = TOKEN_CLOSE; |
|
parser->paren_level--; |
|
if (parser->paren_level < 0) |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unbalanced parenthesis: %s"), parser->cursor - 1); |
|
break; |
|
case '!': |
|
c0 = parser_getc(parser); |
|
if (c0 == '=') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_NE; |
|
break; |
|
} else if (c0 == '~') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_RE_UNMATCH; |
|
break; |
|
} |
|
parser_ungetc(parser, c0); |
|
t->type = TOKEN_OP1; |
|
t->val.op1 = OP1_NOT; |
|
break; |
|
case '<': |
|
t->type = TOKEN_OP2; |
|
c0 = parser_getc(parser); |
|
if (c0 == '=') { |
|
t->val.op2 = OP2_LE; |
|
break; |
|
} |
|
parser_ungetc(parser, c0); |
|
t->val.op2 = OP2_LT; |
|
break; |
|
case '>': |
|
t->type = TOKEN_OP2; |
|
c0 = parser_getc(parser); |
|
if (c0 == '=') { |
|
t->val.op2 = OP2_GE; |
|
break; |
|
} |
|
parser_ungetc(parser, c0); |
|
t->val.op2 = OP2_GT; |
|
break; |
|
case '=': |
|
c0 = parser_getc(parser); |
|
if (c0 == '=') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_EQ; |
|
break; |
|
} else if (c0 == '~') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_RE_MATCH; |
|
break; |
|
} |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected character %c after ="), c0); |
|
break; |
|
case '&': |
|
c0 = parser_getc(parser); |
|
if (c0 == '&') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_AND; |
|
break; |
|
} |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected character %c after ="), c0); |
|
break; |
|
case '|': |
|
c0 = parser_getc(parser); |
|
if (c0 == '|') { |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_OR; |
|
break; |
|
} |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected character %c after ="), c0); |
|
break; |
|
case '"': |
|
case '\'': |
|
t->type = TOKEN_STR; |
|
parser_read_str(parser, t, c); |
|
break; |
|
default: |
|
if (isalpha((unsigned char)c) || c == '_') { |
|
xstrputc(&t->val.str, c); |
|
parser_read_name(parser, t); |
|
if (strcmp(t->val.str, "true") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_TRUE; |
|
} else if (strcmp(t->val.str, "false") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_FALSE; |
|
} else if (strcmp(t->val.str, "or") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_OR; |
|
} else if (strcmp(t->val.str, "and") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_AND; |
|
} else if (strcmp(t->val.str, "eq") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_EQ; |
|
} else if (strcmp(t->val.str, "ne") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_NE; |
|
} else if (strcmp(t->val.str, "lt") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_LT; |
|
} else if (strcmp(t->val.str, "le") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_LE; |
|
} else if (strcmp(t->val.str, "gt") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_GT; |
|
} else if (strcmp(t->val.str, "ge") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP2; |
|
t->val.op2 = OP2_GE; |
|
} else if (strcmp(t->val.str, "not") == 0) { |
|
free(t->val.str); |
|
t->type = TOKEN_OP1; |
|
t->val.op1 = OP1_NOT; |
|
} else |
|
t->type = TOKEN_NAME; |
|
break; |
|
} else if (isdigit((unsigned char)c)) { |
|
t->type = TOKEN_DEC; |
|
xstrputc(&t->val.str, c); |
|
if (parser_read_dec(parser, t) != 0) |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: failed to convert input to number")); |
|
break; |
|
} |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected character %c"), c); |
|
break; |
|
} |
|
return t; |
|
} |
|
|
|
static void parameter_init(struct parameter *param, struct libscols_column *cl) |
|
{ |
|
param->cl = cl; |
|
param->has_value = false; |
|
} |
|
|
|
static struct libscols_column *search_column(struct libscols_table *tb, const char *name) |
|
{ |
|
size_t len = scols_table_get_ncols(tb); |
|
size_t i; |
|
|
|
for (i = 0; i < len; i++) { |
|
struct libscols_column *cl = scols_table_get_column(tb, i); |
|
const char *n = scols_column_get_name(cl); |
|
|
|
if (n && strcmp(n, name) == 0) |
|
return cl; |
|
} |
|
return NULL; |
|
} |
|
|
|
static struct node *dparser_compile1(struct parser *parser, struct node *last) |
|
{ |
|
struct token *t = parser_read(parser); |
|
|
|
if (GOT_ERROR(parser)) { |
|
token_free(t); |
|
return NULL; |
|
} |
|
|
|
if (t->type == TOKEN_EOF) { |
|
token_free(t); |
|
return last; |
|
} |
|
if (t->type == TOKEN_CLOSE) { |
|
token_free(t); |
|
return last; |
|
} |
|
|
|
if (last) { |
|
switch (t->type) { |
|
case TOKEN_NAME: |
|
case TOKEN_STR: |
|
case TOKEN_DEC: |
|
case TOKEN_TRUE: |
|
case TOKEN_FALSE: |
|
case TOKEN_OPEN: |
|
case TOKEN_OP1: |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected token: %s after %s"), t->val.str, |
|
NODE_CLASS(last)->name); |
|
token_free(t); |
|
return NULL; |
|
default: |
|
break; |
|
} |
|
} else { |
|
switch (t->type) { |
|
case TOKEN_OP2: |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: empty left side expression: %s"), |
|
TOKEN_OP2_CLASS(t)->name); |
|
token_free(t); |
|
return NULL; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
struct node *node = NULL; |
|
switch (t->type) { |
|
case TOKEN_NAME: { |
|
int col_id = parser->column_name_to_id(t->val.str, parser->data); |
|
if (col_id == LSFD_FILTER_UNKNOWN_COL_ID) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: no such column: %s"), t->val.str); |
|
token_free(t); |
|
return NULL; |
|
|
|
} |
|
|
|
struct libscols_column *cl = search_column(parser->tb, t->val.str); |
|
if (!cl) { |
|
cl = parser->add_column_by_id(parser->tb, col_id, parser->data); |
|
if (!cl) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: cannot add a column to table: %s"), t->val.str); |
|
token_free(t); |
|
return NULL; |
|
} |
|
scols_column_set_flags(cl, SCOLS_FL_HIDDEN); |
|
} |
|
parameter_init(parser->parameters + col_id, cl); |
|
|
|
int jtype = scols_column_get_json_type(cl); |
|
int ntype; |
|
switch (jtype) { |
|
case SCOLS_JSON_STRING: |
|
ntype = NODE_STR; |
|
break; |
|
case SCOLS_JSON_NUMBER: |
|
ntype = NODE_NUM; |
|
break; |
|
case SCOLS_JSON_BOOLEAN: |
|
ntype = NODE_BOOL; |
|
break; |
|
default: |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unsupported column data type: %d, column: %s"), |
|
jtype, t->val.str); |
|
return NULL; |
|
} |
|
node = node_val_new(ntype, col_id); |
|
token_free(t); |
|
return node; |
|
} |
|
|
|
case TOKEN_STR: |
|
node = node_val_new(NODE_STR, -1); |
|
VAL(node, str) = xstrdup(t->val.str); |
|
token_free(t); |
|
return node; |
|
|
|
case TOKEN_DEC: |
|
node = node_val_new(NODE_NUM, -1); |
|
VAL(node, num) = t->val.num; |
|
token_free(t); |
|
return node; |
|
|
|
case TOKEN_TRUE: |
|
case TOKEN_FALSE: |
|
node = node_val_new(NODE_BOOL, -1); |
|
VAL(node, boolean) = (t->type == TOKEN_TRUE); |
|
token_free(t); |
|
return node; |
|
|
|
case TOKEN_OPEN: |
|
token_free(t); |
|
return dparser_compile(parser); |
|
|
|
case TOKEN_OP1: { |
|
struct node *op1_right = dparser_compile1(parser, NULL); |
|
struct op1_class *op1_class = TOKEN_OP1_CLASS(t); |
|
|
|
token_free(t); |
|
|
|
if (GOT_ERROR(parser)) { |
|
node_free(op1_right); |
|
return NULL; |
|
} |
|
|
|
if (op1_right == NULL) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: empty right side expression: %s"), |
|
op1_class->name); |
|
return NULL; |
|
} |
|
|
|
if (!op1_class->check_type(parser, op1_class, op1_right)) { |
|
node_free(op1_right); |
|
return NULL; |
|
} |
|
|
|
node = xmalloc(sizeof(struct node_op1)); |
|
node->type = NODE_OP1; |
|
((struct node_op1 *)node)->opclass = op1_class; |
|
((struct node_op1 *)node)->arg = op1_right; |
|
|
|
return node; |
|
} |
|
|
|
case TOKEN_OP2: { |
|
struct node *op2_right = dparser_compile1(parser, NULL); |
|
struct op2_class *op2_class = TOKEN_OP2_CLASS(t); |
|
|
|
token_free(t); |
|
|
|
if (GOT_ERROR(parser)) { |
|
node_free(op2_right); |
|
return NULL; |
|
} |
|
if (op2_right == NULL) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: empty right side expression: %s"), |
|
op2_class->name); |
|
return NULL; |
|
} |
|
|
|
if (!op2_class->check_type(parser, op2_class, last, op2_right)) { |
|
node_free(op2_right); |
|
return NULL; |
|
} |
|
|
|
node = xmalloc(sizeof(struct node_op2)); |
|
node->type = NODE_OP2; |
|
((struct node_op2 *)node)->opclass = op2_class; |
|
((struct node_op2 *)node)->args[0] = last; |
|
((struct node_op2 *)node)->args[1] = op2_right; |
|
|
|
return node; |
|
} |
|
|
|
default: |
|
warnx("unexpected token type: %d", t->type); |
|
token_free(t); |
|
return NULL; |
|
} |
|
} |
|
|
|
static struct node *dparser_compile(struct parser *parser) |
|
{ |
|
struct node *node = NULL; |
|
|
|
while (true) { |
|
struct node *node0 = dparser_compile1(parser, node); |
|
if (GOT_ERROR(parser)) { |
|
node_free(node); |
|
return NULL; |
|
} |
|
|
|
if (node == node0) { |
|
if (node == NULL) |
|
strncpy(parser->errmsg, |
|
_("error: empty filter expression"), |
|
ERRMSG_LEN - 1); |
|
return node; |
|
} |
|
node = node0; |
|
} |
|
} |
|
|
|
static struct token *token_new(void) |
|
{ |
|
return xcalloc(1, sizeof(struct token)); |
|
} |
|
|
|
static void token_free(struct token *token) |
|
{ |
|
if (TOKEN_CLASS(token)->free) |
|
TOKEN_CLASS(token)->free(token); |
|
free(token); |
|
} |
|
|
|
#ifdef DEBUG |
|
static void token_dump(struct token *token, FILE *stream) |
|
{ |
|
fprintf(stream, "<%s>", TOKEN_CLASS(token)->name); |
|
if (TOKEN_CLASS(token)->dump) |
|
TOKEN_CLASS(token)->dump(token, stream); |
|
fputc('\n', stream); |
|
} |
|
#endif /* DEBUG */ |
|
|
|
static void token_free_str(struct token *token) |
|
{ |
|
free(token->val.str); |
|
} |
|
|
|
static void token_dump_str(struct token *token, FILE *stream) |
|
{ |
|
fputs(token->val.str, stream); |
|
} |
|
|
|
static void token_dump_num(struct token *token, FILE *stream) |
|
{ |
|
fprintf(stream, "%llu", token->val.num); |
|
} |
|
|
|
static void token_dump_op1(struct token *token, FILE *stream) |
|
{ |
|
fputs(TOKEN_OP1_CLASS(token)->name, stream); |
|
} |
|
|
|
static void token_dump_op2(struct token *token, FILE *stream) |
|
{ |
|
fputs(TOKEN_OP2_CLASS(token)->name, stream); |
|
} |
|
|
|
static struct node *node_val_new(enum node_type type, int pindex) |
|
{ |
|
struct node *node = xmalloc(sizeof(struct node_val)); |
|
node->type = type; |
|
PINDEX(node) = pindex; |
|
return node; |
|
} |
|
|
|
static void node_free(struct node *node) |
|
{ |
|
if (node == NULL) |
|
return; |
|
if (NODE_CLASS(node)->free) |
|
NODE_CLASS(node)->free(node); |
|
free(node); |
|
} |
|
|
|
static bool node_apply(struct node *node, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
if (!node) |
|
return true; |
|
|
|
switch (node->type) { |
|
case NODE_OP1: { |
|
struct node_op1 *node_op1 = (struct node_op1*)node; |
|
return node_op1->opclass->is_acceptable(node_op1->arg, params, ln); |
|
} |
|
case NODE_OP2: { |
|
struct node_op2 *node_op2 = (struct node_op2*)node; |
|
return node_op2->opclass->is_acceptable(node_op2->args[0], node_op2->args[1], params, ln); |
|
} |
|
case NODE_BOOL: |
|
if (PINDEX(node) < 0) |
|
return VAL(node,boolean); |
|
|
|
if (!params[PINDEX(node)].has_value) { |
|
const char *data = scols_line_get_column_data(ln, params[PINDEX(node)].cl); |
|
if (data == NULL) |
|
return false; |
|
params[PINDEX(node)].val.boolean = !*data ? false : |
|
*data == '0' ? false : |
|
*data == 'N' || *data == 'n' ? false : true; |
|
params[PINDEX(node)].has_value = true; |
|
} |
|
return params[PINDEX(node)].val.boolean; |
|
default: |
|
warnx(_("unexpected type in filter application: %s"), NODE_CLASS(node)->name); |
|
return false; |
|
} |
|
} |
|
|
|
static void node_dump(struct node *node, struct parameter *param, int depth, FILE *stream) |
|
{ |
|
int i; |
|
|
|
if (!node) |
|
return; |
|
|
|
for (i = 0; i < depth; i++) |
|
fputc(' ', stream); |
|
fputs(NODE_CLASS(node)->name, stream); |
|
if (NODE_CLASS(node)->dump) |
|
NODE_CLASS(node)->dump(node, param, depth, stream); |
|
} |
|
|
|
static void node_str_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) |
|
{ |
|
if (PINDEX(node) >= 0) |
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); |
|
else |
|
fprintf(stream, ": '%s'\n", VAL(node,str)); |
|
} |
|
|
|
static void node_num_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) |
|
{ |
|
if (PINDEX(node) >= 0) |
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); |
|
else |
|
fprintf(stream, ": %llu\n", VAL(node,num)); |
|
} |
|
|
|
static void node_bool_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) |
|
{ |
|
if (PINDEX(node) >= 0) |
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); |
|
else |
|
fprintf(stream, ": %s\n", |
|
VAL(node,boolean) |
|
? token_classes[TOKEN_TRUE].name |
|
: token_classes[TOKEN_FALSE].name); |
|
} |
|
|
|
static void node_re_dump(struct node *node, struct parameter* params __attribute__((__unused__)), |
|
int depth __attribute__((__unused__)), FILE *stream) |
|
{ |
|
fprintf(stream, ": #<regexp %p>\n", &VAL(node,re)); |
|
} |
|
|
|
static void node_op1_dump(struct node *node, struct parameter* params, int depth, FILE *stream) |
|
{ |
|
fprintf(stream, ": %s\n", ((struct node_op1 *)node)->opclass->name); |
|
node_dump(((struct node_op1 *)node)->arg, params, depth + 4, stream); |
|
} |
|
|
|
static void node_op2_dump(struct node *node, struct parameter* params, int depth, FILE *stream) |
|
{ |
|
int i; |
|
|
|
fprintf(stream, ": %s\n", ((struct node_op2 *)node)->opclass->name); |
|
for (i = 0; i < 2; i++) |
|
node_dump(((struct node_op2 *)node)->args[i], params, depth + 4, stream); |
|
} |
|
|
|
static void node_str_free(struct node *node) |
|
{ |
|
if (PINDEX(node) < 0) |
|
free(VAL(node,str)); |
|
} |
|
|
|
static void node_re_free(struct node *node) |
|
{ |
|
regfree(&VAL(node,re)); |
|
} |
|
|
|
static void node_op1_free(struct node *node) |
|
{ |
|
node_free(((struct node_op1 *)node)->arg); |
|
} |
|
|
|
static void node_op2_free(struct node *node) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < 2; i++) |
|
node_free(((struct node_op2 *)node)->args[i]); |
|
} |
|
|
|
static bool op1_not(struct node *node, struct parameter* params, struct libscols_line * ln) |
|
{ |
|
return !node_apply(node, params, ln); |
|
} |
|
|
|
static bool op1_check_type_bool_or_op(struct parser* parser, struct op1_class *op1_class, |
|
struct node *node) |
|
{ |
|
if (! (node->type == NODE_OP1 || node->type == NODE_OP2 || node->type == NODE_BOOL)) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected operand type %s for: %s"), |
|
NODE_CLASS(node)->name, |
|
op1_class->name); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
#define OP2_GET_STR(NODE,DEST) do { \ |
|
int pindex = PINDEX(NODE); \ |
|
if (pindex < 0) \ |
|
DEST = VAL(NODE,str); \ |
|
else { \ |
|
struct parameter *p = params + pindex; \ |
|
if (!p->has_value) { \ |
|
p->val.str = scols_line_get_column_data(ln, p->cl); \ |
|
if (p->val.str == NULL) return false; \ |
|
p->has_value = true; \ |
|
} \ |
|
DEST = p->val.str; \ |
|
} \ |
|
} while(0) |
|
|
|
#define OP2_GET_NUM(NODE,DEST) do { \ |
|
int pindex = PINDEX(NODE); \ |
|
if (pindex < 0) \ |
|
DEST = VAL(NODE,num); \ |
|
else { \ |
|
struct parameter *p = params + pindex; \ |
|
if (!p->has_value) { \ |
|
const char *tmp = scols_line_get_column_data(ln, p->cl); \ |
|
if (tmp == NULL) return false; \ |
|
p->val.num = strtoull(tmp, NULL, 10); \ |
|
p->has_value = true; \ |
|
} \ |
|
DEST = p->val.num; \ |
|
} \ |
|
} while(0) |
|
|
|
#define OP2_EQ_BODY(OP,ELSEVAL) do { \ |
|
if (left->type == NODE_STR) { \ |
|
const char *lv, *rv; \ |
|
OP2_GET_STR(left,lv); \ |
|
OP2_GET_STR(right,rv); \ |
|
return strcmp(lv, rv) OP 0; \ |
|
} else if (left->type == NODE_NUM) { \ |
|
unsigned long long lv, rv; \ |
|
OP2_GET_NUM(left,lv); \ |
|
OP2_GET_NUM(right,rv); \ |
|
return lv OP rv; \ |
|
} else { \ |
|
return node_apply(left, params, ln) OP node_apply(right, params, ln); \ |
|
} \ |
|
} while(0) |
|
|
|
#define OP2_CMP_BODY(OP) do { \ |
|
unsigned long long lv, rv; \ |
|
OP2_GET_NUM(left,lv); \ |
|
OP2_GET_NUM(right,rv); \ |
|
return (lv OP rv); \ |
|
} while(0) |
|
static bool op2_eq(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_EQ_BODY(==, false); |
|
} |
|
|
|
static bool op2_ne(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_EQ_BODY(!=, true); |
|
} |
|
|
|
static bool op2_and(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
return node_apply(left, params, ln) && node_apply(right, params, ln); |
|
} |
|
|
|
static bool op2_or(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
return node_apply(left, params, ln) || node_apply(right, params, ln); |
|
} |
|
|
|
static bool op2_lt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_CMP_BODY(<); |
|
} |
|
|
|
static bool op2_le(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_CMP_BODY(<=); |
|
} |
|
|
|
static bool op2_gt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_CMP_BODY(>); |
|
} |
|
|
|
static bool op2_ge(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) |
|
{ |
|
OP2_CMP_BODY(>=); |
|
} |
|
|
|
static bool op2_re_match(struct node *left, struct node *right, |
|
struct parameter *params, struct libscols_line *ln) |
|
{ |
|
const char *str; |
|
OP2_GET_STR(left, str); |
|
|
|
return (regexec(&VAL(right,re), str, 0, NULL, 0) == 0); |
|
} |
|
|
|
static bool op2_re_unmatch(struct node *left, struct node *right, |
|
struct parameter *params, struct libscols_line *ln) |
|
{ |
|
return !op2_re_match(left, right, params, ln); |
|
} |
|
|
|
static bool op2_check_type_boolean_or_op(struct parser* parser, struct op2_class *op2_class, |
|
struct node *left, struct node *right) |
|
{ |
|
enum node_type lt = left->type, rt = right->type; |
|
|
|
if (!(lt == NODE_OP1 || lt == NODE_OP2 || lt == NODE_BOOL)) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected left operand type %s for: %s"), |
|
NODE_CLASS(left)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
if (! (rt == NODE_OP1 || rt == NODE_OP2 || rt == NODE_BOOL)) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected right operand type %s for: %s"), |
|
NODE_CLASS(right)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool op2_check_type_eq_or_bool_or_op(struct parser* parser, struct op2_class *op2_class, |
|
struct node *left, struct node *right) |
|
{ |
|
enum node_type lt = left->type, rt = right->type; |
|
|
|
if (lt == rt) |
|
return true; |
|
|
|
return op2_check_type_boolean_or_op(parser, op2_class, left, right); |
|
} |
|
|
|
static bool op2_check_type_num(struct parser* parser, struct op2_class *op2_class, |
|
struct node *left, struct node *right) |
|
{ |
|
if (left->type != NODE_NUM) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected left operand type %s for: %s"), |
|
NODE_CLASS(left)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
if (right->type != NODE_NUM) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected right operand type %s for: %s"), |
|
NODE_CLASS(right)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool op2_check_type_re(struct parser* parser, struct op2_class *op2_class, |
|
struct node *left, struct node *right) |
|
{ |
|
if (left->type != NODE_STR) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected left operand type %s for: %s"), |
|
NODE_CLASS(left)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
if (right->type != NODE_STR) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: unexpected right operand type %s for: %s"), |
|
NODE_CLASS(right)->name, |
|
op2_class->name); |
|
return false; |
|
} |
|
if (PINDEX(right) >= 0) { |
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: string literal is expected as right operand for: %s"), |
|
op2_class->name); |
|
return false; |
|
} |
|
|
|
char *regex = VAL(right, str); |
|
VAL(right, str) = NULL; |
|
|
|
int err = regcomp(&VAL(right, re), regex, REG_NOSUB | REG_EXTENDED); |
|
if (err != 0) { |
|
size_t size = regerror(err, &VAL(right, re), NULL, 0); |
|
char *buf = xmalloc(size + 1); |
|
|
|
regerror(err, &VAL(right, re), buf, size); |
|
|
|
snprintf(parser->errmsg, ERRMSG_LEN, |
|
_("error: could not compile regular expression %s: %s"), |
|
regex, buf); |
|
free(buf); |
|
return false; |
|
} |
|
right->type = NODE_RE; |
|
free(regex); |
|
return true; |
|
} |
|
|
|
struct lsfd_filter *lsfd_filter_new(const char *const expr, struct libscols_table *tb, |
|
int ncols, |
|
int (*column_name_to_id)(const char *, void *), |
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*), |
|
void *data) |
|
{ |
|
struct parser parser; |
|
int i; |
|
struct node *node; |
|
struct lsfd_filter *filter; |
|
|
|
parser_init(&parser, expr, tb, ncols, |
|
column_name_to_id, |
|
add_column_by_id, |
|
data); |
|
|
|
node = dparser_compile(&parser); |
|
|
|
filter = xmalloc(sizeof(struct lsfd_filter)); |
|
filter->errmsg[0] = '\0'; |
|
if (GOT_ERROR(&parser)) { |
|
strcpy(filter->errmsg, parser.errmsg); |
|
return filter; |
|
} |
|
assert(node); |
|
if (parser.paren_level > 0) { |
|
node_free(node); |
|
strncpy(filter->errmsg, _("error: unbalanced parenthesis: ("), ERRMSG_LEN - 1); |
|
return filter; |
|
} |
|
if (*parser.cursor != '\0') { |
|
node_free(node); |
|
snprintf(filter->errmsg, ERRMSG_LEN, |
|
_("error: garbage at the end of expression: %s"), parser.cursor); |
|
return filter; |
|
} |
|
if (node->type == NODE_STR || node->type == NODE_NUM) { |
|
node_free(node); |
|
snprintf(filter->errmsg, ERRMSG_LEN, |
|
_("error: bool expression is expected: %s"), expr); |
|
return filter; |
|
} |
|
|
|
filter->table = tb; |
|
scols_ref_table(filter->table); |
|
filter->node = node; |
|
filter->parameters = parser.parameters; |
|
filter->nparams = ncols; |
|
for (i = 0; i < filter->nparams; i++) { |
|
if (filter->parameters[i].cl) |
|
scols_ref_column(filter->parameters[i].cl); |
|
} |
|
return filter; |
|
} |
|
|
|
const char *lsfd_filter_get_errmsg(struct lsfd_filter *filter) |
|
{ |
|
if (GOT_ERROR(filter)) |
|
return filter->errmsg; |
|
|
|
return NULL; |
|
} |
|
|
|
void lsfd_filter_dump(struct lsfd_filter *filter, FILE *stream) |
|
{ |
|
if (!filter) { |
|
fputs("EMPTY\n", stream); |
|
return; |
|
} |
|
|
|
if (GOT_ERROR(filter)) { |
|
fprintf(stream, "ERROR: %s\n", filter->errmsg); |
|
return; |
|
} |
|
|
|
node_dump(filter->node, filter->parameters, 0, stream); |
|
} |
|
|
|
void lsfd_filter_free(struct lsfd_filter *filter) |
|
{ |
|
int i; |
|
|
|
if (!filter) |
|
return; |
|
|
|
if (!GOT_ERROR(filter)) { |
|
for (i = 0; i < filter->nparams; i++) { |
|
if (filter->parameters[i].cl) |
|
scols_unref_column(filter->parameters[i].cl); |
|
} |
|
scols_unref_table(filter->table); |
|
node_free(filter->node); |
|
} |
|
free(filter->parameters); |
|
free(filter); |
|
} |
|
|
|
bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line * ln) |
|
{ |
|
int i; |
|
|
|
if (!filter) |
|
return true; |
|
|
|
if (GOT_ERROR(filter)) |
|
return false; |
|
|
|
for (i = 0; i < filter->nparams; i++) |
|
filter->parameters[i].has_value = false; |
|
|
|
return node_apply(filter->node, filter->parameters, ln); |
|
}
|
|
|