]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: add filter engine
authorMasatake YAMATO <yamato@redhat.com>
Mon, 20 Sep 2021 17:57:37 +0000 (02:57 +0900)
committerKarel Zak <kzak@redhat.com>
Wed, 6 Oct 2021 09:01:54 +0000 (11:01 +0200)
Signed-off-by: Masatake YAMATO <yamato@redhat.com>
misc-utils/Makemodule.am
misc-utils/lsfd-filter.c [new file with mode: 0644]
misc-utils/lsfd-filter.h [new file with mode: 0644]

index cba0880c8c8bf95f6f5f667656f1dbd4d6c2361f..bfaffee81c27ab47894a962acc553412858660f6 100644 (file)
@@ -252,6 +252,8 @@ bin_PROGRAMS += lsfd
 lsfd_SOURCES = \
        misc-utils/lsfd.c \
        misc-utils/lsfd.h \
+       misc-utils/lsfd-filter.h \
+       misc-utils/lsfd-filter.c \
        misc-utils/lsfd-file.c \
        misc-utils/lsfd-cdev.c \
        misc-utils/lsfd-bdev.c \
diff --git a/misc-utils/lsfd-filter.c b/misc-utils/lsfd-filter.c
new file mode 100644 (file)
index 0000000..c509b05
--- /dev/null
@@ -0,0 +1,1256 @@
+/*
+ * lsfd-filter.c - filtering engine for lsfd
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "lsfd-filter.h"
+
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * 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: =~, !~ (regex match)
+                                *       =*, !* (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,
+};
+
+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;
+};
+
+enum node_type {
+       NODE_STR,
+       NODE_NUM,
+       NODE_BOOL,
+       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 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 node *, struct node *);
+};
+
+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];
+};
+
+#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;
+       } 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 *);
+static void          token_dump(struct token *, FILE *);
+
+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 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_check_type_eq_or_bool_or_op(struct node *, struct node *);
+static bool op2_check_type_boolean_or_op   (struct node *, struct node *);
+static bool op2_check_type_num             (struct node *, struct node *);
+
+static void node_str_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_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])
+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]))
+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]))
+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,
+       },
+};
+
+#define NODE_CLASS(NODE) (&node_classes[(NODE)->type])
+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_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;
+               }
+               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;
+               }
+               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 const char *get_column_name(struct libscols_column *cl)
+{
+       struct libscols_cell *header = scols_column_get_header(cl);
+       return scols_cell_get_data(header);;
+}
+
+static struct libscols_column *search_column(struct libscols_table *tb, const char *name)
+{
+       size_t len = scols_table_get_ncols(tb);
+       for (size_t i = 0; i < len; i++) {
+               struct libscols_column *cl = scols_table_get_column(tb, i);
+               const char *n = get_column_name(cl);
+               if (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 == -1) {
+                       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);
+               token_free(t);
+
+               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);
+               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(op1_right)) {
+                       snprintf(parser->errmsg, ERRMSG_LEN,
+                                _("error: unexpected operand type for: %s"),
+                                op1_class->name);
+                       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(last, op2_right)) {
+                       snprintf(parser->errmsg, ERRMSG_LEN,
+                                _("error: unexpected operand type(s) for: %s"),
+                                op2_class->name);
+                       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);
+               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)
+                       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);
+}
+
+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);
+}
+
+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) {
+                       struct libscols_cell *cell = scols_line_get_column_cell(ln,
+                                                                               params[PINDEX(node)].cl);
+                       const char *data = scols_cell_get_data(cell);
+                       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)
+{
+       if (!node)
+               return;
+
+       for (int 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", get_column_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", get_column_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", get_column_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_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)
+{
+       fprintf(stream, ": %s\n", ((struct node_op2 *)node)->opclass->name);
+       for (int 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_op1_free(struct node *node)
+{
+       node_free(((struct node_op1 *)node)->arg);
+}
+
+static void node_op2_free(struct node *node)
+{
+       for (int 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 node *node)
+{
+       return (node->type == NODE_OP1 || node->type == NODE_OP2 || node->type == NODE_BOOL);
+}
+
+#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) {                                    \
+                       struct libscols_cell *cell = scols_line_get_column_cell(ln, p->cl); \
+                       p->val.str = scols_cell_get_data(cell);         \
+                       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) {                                    \
+                       struct libscols_cell *cell = scols_line_get_column_cell(ln, p->cl); \
+                       const char *tmp = scols_cell_get_data(cell);    \
+                       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_check_type_boolean_or_op(struct node *left, struct node *right)
+{
+       enum node_type lt = left->type, rt = right->type;
+
+       if ((lt == NODE_BOOL || lt == NODE_OP1 || lt == NODE_OP2)
+           && (rt == NODE_BOOL || rt == NODE_OP1 || rt == NODE_OP2))
+               return true;
+
+       return false;
+}
+
+static bool op2_check_type_eq_or_bool_or_op(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(left, right);
+}
+
+static bool op2_check_type_num(struct node *left, struct node *right)
+{
+       return (left->type == NODE_NUM && right->type == NODE_NUM);
+}
+
+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;
+       parser_init(&parser, expr, tb, ncols,
+                   column_name_to_id,
+                   add_column_by_id,
+                   data);
+
+       struct node *node = dparser_compile(&parser);
+
+       struct lsfd_filter *filter = xmalloc(sizeof(struct lsfd_filter));
+       filter->errmsg[0] = '\0';
+       if (GOT_ERROR(&parser)) {
+               strcpy(filter->errmsg, parser.errmsg);
+               return filter;
+       }
+       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 (int 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)
+{
+       if (!filter)
+               return;
+
+       if (!GOT_ERROR(filter)) {
+               for (int 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);
+}
+
+bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line * ln)
+{
+       if (!filter)
+               return true;
+
+       if (GOT_ERROR(filter))
+               return false;
+
+       for (int i = 0; i < filter->nparams; i++)
+               filter->parameters[i].has_value = false;
+
+       return node_apply(filter->node, filter->parameters, ln);
+}
diff --git a/misc-utils/lsfd-filter.h b/misc-utils/lsfd-filter.h
new file mode 100644 (file)
index 0000000..9b2dd60
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * lsfd-filter.c - filtering engine for lsfd
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef UTIL_LINUX_LSFD_FILTER_H
+#define UTIL_LINUX_LSFD_FILTER_H
+
+#include "libsmartcols.h"
+#include <stdio.h>
+#include <stdbool.h>
+
+struct lsfd_filter;
+
+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);
+
+/* Call lsfd_filter_get_errmsg() after lsfd_filter_new() to detect
+ * whether lsfd_filter_new() is failed or not. Returning NULL means,
+ * lsfd_filter_new() is successful. */
+const char *lsfd_filter_get_errmsg(struct lsfd_filter *filter);
+void lsfd_filter_free(struct lsfd_filter *filter);
+bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line *ln);
+
+/* Dumping AST. */
+void lsfd_filter_dump(struct lsfd_filter *filter, FILE *stream);
+
+#endif /* UTIL_LINUX_LSFD_FILTER_H */