/* * lsfd-filter.c - filtering engine for lsfd * * Copyright (C) 2021 Red Hat, Inc. * Copyright (C) 2021 Masatake YAMATO * * 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 #include #include /* 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) 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, ": #\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); }