diff options
Diffstat (limited to 'libsmartcols/src/filter-param.c')
-rw-r--r-- | libsmartcols/src/filter-param.c | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/libsmartcols/src/filter-param.c b/libsmartcols/src/filter-param.c new file mode 100644 index 0000000..f7d243d --- /dev/null +++ b/libsmartcols/src/filter-param.c @@ -0,0 +1,889 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <regex.h> + +#include "rpmatch.h" +#include "smartcolsP.h" + +struct filter_param { + struct filter_node node; + int type; + enum filter_holder holder; + + union { + char *str; + unsigned long long num; + long double fnum; + bool boolean; + } val; + + struct list_head pr_params; + struct libscols_column *col; + char *holder_name; + regex_t *re; + + unsigned int fetched :1, /* holder requested */ + empty : 1; +}; + +static int cast_param(int type, struct filter_param *n); + +static inline const char *datatype2str(int type) +{ + static const char *types[] = { + [SCOLS_DATA_NONE] = "none", + [SCOLS_DATA_STRING] = "string", + [SCOLS_DATA_U64] = "u64", + [SCOLS_DATA_FLOAT] = "float", + [SCOLS_DATA_BOOLEAN] = "boolean" + }; + return types[type]; +} + +static char *rem_quotation(const char *p, int c) +{ + size_t len = strlen(p); + + if (*(p + (len - 1)) == c) + len -= 2; + return strndup(p + 1, len); +} + +static int param_set_data(struct filter_param *n, int type, const void *data) +{ + const char *p; + + /*DBG(FPARAM, ul_debugobj(n, " set %s data", datatype2str(type)));*/ + + switch (type) { + case SCOLS_DATA_STRING: + p = data; + if (p && (*p == '"' || *p == '\'')) + n->val.str = rem_quotation(p, *p); + else if (data) + n->val.str = strdup((char *) data); + if (data && !n->val.str) + return -ENOMEM; + if (data) { + rtrim_whitespace((unsigned char *) n->val.str); + ltrim_whitespace((unsigned char *) n->val.str); + } + break; + case SCOLS_DATA_U64: + n->val.num = data ? *((unsigned long long *) data) : 0; + break; + case SCOLS_DATA_FLOAT: + n->val.fnum = data ? *((long double *) data) : 0; + break; + case SCOLS_DATA_BOOLEAN: + n->val.boolean = data ? (*((bool *) data) == 0 ? 0 : 1) : 0; + break; + default: + return 0; + } + + n->type = type; + n->empty = data == NULL; + return 0; +} + +struct filter_node *filter_new_param( + struct libscols_filter *fltr, + int type, + enum filter_holder holder, + void *data) +{ + struct filter_param *n = (struct filter_param *) __filter_new_node( + F_NODE_PARAM, + sizeof(struct filter_param)); + if (!n) + return NULL; + + n->type = type; + n->holder = holder; + INIT_LIST_HEAD(&n->pr_params); + + if (param_set_data(n, type, data) != 0) { + filter_free_param(n); + return NULL; + } + + if (holder == F_HOLDER_COLUMN) { + n->holder_name = strdup((char *) data); + DBG(FLTR, ul_debugobj(fltr, "new %s holder", n->holder_name)); + } + + if (fltr) + list_add_tail(&n->pr_params, &fltr->params); + + return (struct filter_node *) n; +} + +int filter_compile_param(struct libscols_filter *fltr, struct filter_param *n) +{ + int rc; + + if (n->re) + return 0; + if (!n->val.str) + return -EINVAL; + + n->re = calloc(1, sizeof(regex_t)); + if (!n->re) + return -ENOMEM; + + rc = regcomp(n->re, n->val.str, REG_NOSUB | REG_EXTENDED); + if (rc) { + size_t size = regerror(rc, n->re, NULL, 0); + + fltr->errmsg = malloc(size + 1); + if (!fltr->errmsg) + return -ENOMEM; + regerror(rc, n->re, fltr->errmsg, size); + return -EINVAL; + } + return 0; +} + +static struct filter_param *copy_param(struct filter_param *n) +{ + void *data = NULL; + + switch (n->type) { + case SCOLS_DATA_STRING: + data = n->val.str; + break; + case SCOLS_DATA_U64: + data = &n->val.num; + break; + case SCOLS_DATA_FLOAT: + data = &n->val.fnum; + break; + case SCOLS_DATA_BOOLEAN: + data = &n->val.boolean; + break; + } + + DBG(FPARAM, ul_debugobj(n, "copying")); + return (struct filter_param *) filter_new_param(NULL, n->type, F_HOLDER_NONE, data); +} + +static void param_reset_data(struct filter_param *n) +{ + if (n->type == SCOLS_DATA_STRING) + free(n->val.str); + + memset(&n->val, 0, sizeof(n->val)); + n->fetched = 0; + n->empty = 1; + + if (n->re) { + regfree(n->re); + free(n->re); + n->re = NULL; + } +} + +void filter_free_param(struct filter_param *n) +{ + param_reset_data(n); + + free(n->holder_name); + list_del_init(&n->pr_params); + scols_unref_column(n->col); + free(n); +} + +int filter_param_get_datatype(struct filter_param *n) +{ + return n ? n->type : SCOLS_DATA_NONE; +} + +int is_filter_holder_node(struct filter_node *n) +{ + return n && filter_node_get_type(n) == F_NODE_PARAM + && ((struct filter_param *)(n))->holder; +} + +void filter_dump_param(struct ul_jsonwrt *json, struct filter_param *n) +{ + ul_jsonwrt_object_open(json, "param"); + + if (n->empty) { + ul_jsonwrt_value_boolean(json, "empty", true); + ul_jsonwrt_value_s(json, "type", datatype2str(n->type)); + } else { + switch (n->type) { + case SCOLS_DATA_STRING: + ul_jsonwrt_value_s(json, "string", n->val.str); + break; + case SCOLS_DATA_U64: + ul_jsonwrt_value_u64(json, "number", n->val.num); + break; + case SCOLS_DATA_FLOAT: + ul_jsonwrt_value_double(json, "float", n->val.fnum); + break; + case SCOLS_DATA_BOOLEAN: + ul_jsonwrt_value_boolean(json, "bool", n->val.boolean); + break; + default: + break; + } + } + + if (n->holder == F_HOLDER_COLUMN) + ul_jsonwrt_value_s(json, "column", n->holder_name); + + ul_jsonwrt_object_close(json); +} + +int filter_param_reset_holder(struct filter_param *n) +{ + if (!n->holder || !n->col) + return 0; + + param_reset_data(n); + + if (n->type != SCOLS_DATA_NONE) + return 0; /* already set */ + + if (scols_column_get_data_type(n->col)) + /* use by application defined type */ + n->type = scols_column_get_data_type(n->col); + else { + /* use by JSON defined type, default to string if not specified */ + switch (n->col->json_type) { + case SCOLS_JSON_NUMBER: + n->type = SCOLS_DATA_U64; + break; + case SCOLS_JSON_BOOLEAN: + n->type = SCOLS_DATA_BOOLEAN; + break; + case SCOLS_JSON_FLOAT: + n->type = SCOLS_DATA_FLOAT; + break; + case SCOLS_JSON_STRING: + default: + n->type = SCOLS_DATA_STRING; + break; + } + } + + DBG(FPARAM, ul_debugobj(n, "holder %s type: %s", n->holder_name, datatype2str(n->type))); + return 0; +} + +static int fetch_holder_data(struct libscols_filter *fltr __attribute__((__unused__)), + struct filter_param *n, struct libscols_line *ln) +{ + const char *data = NULL; + struct libscols_column *cl = n->col; + int type = n->type; + int rc = 0; + + if (n->fetched || n->holder != F_HOLDER_COLUMN) + return 0; + if (!cl) { + DBG(FPARAM, ul_debugobj(n, "no column for %s holder", n->holder_name)); + return -EINVAL; + } + DBG(FPARAM, ul_debugobj(n, "fetching %s data", n->holder_name)); + + if (fltr->filler_cb && !scols_line_is_filled(ln, cl->seqnum)) { + DBG(FPARAM, ul_debugobj(n, " by callback")); + rc = fltr->filler_cb(fltr, ln, cl->seqnum, fltr->filler_data); + if (rc) + return rc; + } + + n->fetched = 1; + + if (scols_column_has_data_func(cl)) { + struct libscols_cell *ce = scols_line_get_column_cell(ln, cl); + + DBG(FPARAM, ul_debugobj(n, " using datafunc()")); + if (ce) + data = cl->datafunc(n->col, ce, cl->datafunc_data); + if (data) + rc = param_set_data(n, scols_column_get_data_type(cl), data); + } else { + DBG(FPARAM, ul_debugobj(n, " using as string")); + data = scols_line_get_column_data(ln, n->col); + rc = param_set_data(n, SCOLS_DATA_STRING, data); + } + + /* cast to the wanted type */ + if (rc == 0 && type != SCOLS_DATA_NONE) + rc = cast_param(type, n); + return rc; +} + +int filter_eval_param(struct libscols_filter *fltr, + struct libscols_line *ln, + struct filter_param *n, + int *status) +{ + int rc = 0; + + DBG(FLTR, ul_debugobj(fltr, "eval param")); + + rc = fetch_holder_data(fltr, n, ln); + if (n->empty || rc) { + *status = 0; + goto done; + } + + switch (n->type) { + case SCOLS_DATA_STRING: + *status = n->val.str != NULL && *n->val.str != '\0'; + break; + case SCOLS_DATA_U64: + *status = n->val.num != 0; + break; + case SCOLS_DATA_FLOAT: + *status = n->val.fnum != 0.0; + break; + case SCOLS_DATA_BOOLEAN: + *status = n->val.boolean != false; + break; + default: + rc = -EINVAL; + break; + } +done: + if (rc) + DBG(FLTR, ul_debugobj(fltr, "failed eval param [rc=%d]", rc)); + return rc; +} + +int filter_count_param(struct libscols_filter *fltr, + struct libscols_line *ln, + struct libscols_counter *ct) +{ + unsigned long long num = 0; + + if (ct->func == SCOLS_COUNTER_COUNT) { + ct->result++; + return 0; + } + + if (ct->param) { + int rc; + + ct->param->type = SCOLS_DATA_U64; + rc = fetch_holder_data(fltr, ct->param, ln); + if (rc) + return rc; + + if (ct->param->empty) + return -EINVAL; + + num = ct->param->val.num; + } + + switch (ct->func) { + case SCOLS_COUNTER_MAX: + if (!ct->has_result) + ct->result = num; + else if (num > ct->result) + ct->result = num; + break; + case SCOLS_COUNTER_MIN: + if (!ct->has_result) + ct->result = num; + else if (num < ct->result) + ct->result = num; + break; + case SCOLS_COUNTER_SUM: + ct->result += num; + break; + default: + return -EINVAL; + } + + ct->has_result = 1; + DBG(FLTR, ul_debugobj(fltr, "counted '%s' [result: %llu]", ct->name, ct->result)); + return 0; +} + +static int xstrcmp(char *a, char *b) +{ + if (!a && !b) + return 0; + if (!a && b) + return -1; + if (a && !b) + return 1; + return strcmp(a, b); +} + +static int string_opers(enum filter_etype oper, struct filter_param *l, + struct filter_param *r, int *status) +{ + switch (oper) { + case F_EXPR_EQ: + *status = xstrcmp(l->val.str, r->val.str) == 0; + break; + case F_EXPR_NE: + *status = xstrcmp(l->val.str, r->val.str) != 0; + break; + case F_EXPR_LE: + *status = xstrcmp(l->val.str, r->val.str) <= 0; + break; + case F_EXPR_LT: + *status = xstrcmp(l->val.str, r->val.str) < 0; + break; + case F_EXPR_GE: + *status = xstrcmp(l->val.str, r->val.str) >= 0; + break; + case F_EXPR_GT: + *status = xstrcmp(l->val.str, r->val.str) > 0; + break; + case F_EXPR_REG: + if (!r->re) + return -EINVAL; + *status = regexec(r->re, l->val.str ? : "", 0, NULL, 0) == 0; + break; + case F_EXPR_NREG: + if (!r->re) + return -EINVAL; + *status = regexec(r->re, l->val.str ? : "", 0, NULL, 0) != 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static int u64_opers(enum filter_etype oper, struct filter_param *l, + struct filter_param *r, int *status) +{ + if (l->empty || r->empty) { + *status = 0; + return 0; + } + + switch (oper) { + case F_EXPR_EQ: + *status = l->val.num == r->val.num; + break; + case F_EXPR_NE: + *status = l->val.num != r->val.num; + break; + case F_EXPR_LE: + *status = l->val.num <= r->val.num; + break; + case F_EXPR_LT: + *status = l->val.num < r->val.num; + break; + case F_EXPR_GE: + *status = l->val.num >= r->val.num; + break; + case F_EXPR_GT: + *status = l->val.num > r->val.num; + break; + default: + return -EINVAL; + } + return 0; +} + +static int float_opers(enum filter_etype oper, struct filter_param *l, + struct filter_param *r, int *status) +{ + if (l->empty || r->empty) { + *status = 0; + return 0; + } + + switch (oper) { + case F_EXPR_EQ: + *status = l->val.fnum == r->val.fnum; + break; + case F_EXPR_NE: + *status = l->val.fnum != r->val.fnum; + break; + case F_EXPR_LE: + *status = l->val.fnum <= r->val.fnum; + break; + case F_EXPR_LT: + *status = l->val.fnum < r->val.fnum; + break; + case F_EXPR_GE: + *status = l->val.fnum >= r->val.fnum; + break; + case F_EXPR_GT: + *status = l->val.fnum > r->val.fnum; + break; + default: + return -EINVAL; + } + return 0; +} + +static int bool_opers(enum filter_etype oper, struct filter_param *l, + struct filter_param *r, int *status) +{ + if (l->empty || r->empty) { + *status = 0; + return 0; + } + + switch (oper) { + case F_EXPR_EQ: + *status = l->val.boolean == r->val.boolean; + break; + case F_EXPR_NE: + *status = l->val.boolean != r->val.boolean; + break; + case F_EXPR_LE: + *status = l->val.boolean <= r->val.boolean; + break; + case F_EXPR_LT: + *status = l->val.boolean < r->val.boolean; + break; + case F_EXPR_GE: + *status = l->val.boolean >= r->val.boolean; + break; + case F_EXPR_GT: + *status = l->val.boolean > r->val.boolean; + break; + default: + return -EINVAL; + } + return 0; +} + +/* call filter_cast_param() to be sure that param data are ready (fetched from + * holder, etc.) */ +int filter_compare_params(struct libscols_filter *fltr __attribute__((__unused__)), + enum filter_etype oper, + struct filter_param *l, + struct filter_param *r, + int *status) +{ + int rc; + + if (!l || !r || l->type != r->type) + return -EINVAL; + + *status = 0; + + switch (l->type) { + case SCOLS_DATA_STRING: + rc = string_opers(oper, l, r, status); + break; + case SCOLS_DATA_U64: + rc = u64_opers(oper, l, r, status); + break; + case SCOLS_DATA_FLOAT: + rc = float_opers(oper, l, r, status); + break; + case SCOLS_DATA_BOOLEAN: + rc = bool_opers(oper, l, r, status); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int string_cast(int type, struct filter_param *n) +{ + char *str = n->val.str; + + if (type == SCOLS_DATA_STRING) + return 0; + + n->val.str = NULL; + + switch (type) { + case SCOLS_DATA_U64: + { + uint64_t num = 0; + if (str) { + int rc = ul_strtou64(str, &num, 10); + if (rc) + return rc; + } + n->val.num = num; + break; + } + case SCOLS_DATA_FLOAT: + { + long double num = 0; + if (str) { + int rc = ul_strtold(str, &num); + if (rc) + return rc; + } + n->val.fnum = num; + break; + } + case SCOLS_DATA_BOOLEAN: + { + bool x = str && *str + && (strcasecmp(str, "1") == 0 + || strcasecmp(str, "true") == 0 + || rpmatch(str) == RPMATCH_YES); + n->val.boolean = x; + break; + } + default: + return -EINVAL; + } + + free(str); + return 0; +} + +static int u64_cast(int type, struct filter_param *n) +{ + unsigned long long num = n->val.num; + + switch (type) { + case SCOLS_DATA_STRING: + n->val.str = NULL; + if (asprintf(&n->val.str, "%llu", num) <= 0) + return -ENOMEM; + break; + case SCOLS_DATA_U64: + break; + case SCOLS_DATA_FLOAT: + n->val.fnum = num; + break; + case SCOLS_DATA_BOOLEAN: + n->val.boolean = num > 0 ? true : false; + break; + default: + return -EINVAL; + } + return 0; +} + +static int float_cast(int type, struct filter_param *n) +{ + long double fnum = n->val.fnum; + + switch (type) { + case SCOLS_DATA_STRING: + n->val.str = NULL; + if (asprintf(&n->val.str, "%Lg", fnum) <= 0) + return -ENOMEM; + break; + case SCOLS_DATA_U64: + n->val.num = fnum; + break; + case SCOLS_DATA_FLOAT: + break;; + case SCOLS_DATA_BOOLEAN: + n->val.boolean = fnum > 0.0 ? true : false; + break; + default: + return -EINVAL; + } + return 0; +} + +static int bool_cast(int type, struct filter_param *n) +{ + bool x = n->val.boolean; + + switch (type) { + case SCOLS_DATA_STRING: + n->val.str = NULL; + if (asprintf(&n->val.str, "%s", x ? "true" : "false") <= 0) + return -ENOMEM; + break; + case SCOLS_DATA_U64: + n->val.num = x ? 1 : 0; + break; + case SCOLS_DATA_FLOAT: + n->val.fnum = x ? 1.0 : 0.0; + break; + case SCOLS_DATA_BOOLEAN: + break;; + default: + return -EINVAL; + } + return 0; +} + +static int cast_param(int type, struct filter_param *n) +{ + int rc; + int orgtype = n->type; + + if (type == orgtype) + return 0; + + if (orgtype == SCOLS_DATA_STRING) + DBG(FPARAM, ul_debugobj(n, " casting \"%s\" to %s", n->val.str, datatype2str(type))); + else + DBG(FPARAM, ul_debugobj(n, " casting %s to %s", datatype2str(orgtype), datatype2str(type))); + + switch (orgtype) { + case SCOLS_DATA_STRING: + rc = string_cast(type, n); + break; + case SCOLS_DATA_U64: + rc = u64_cast(type, n); + break; + case SCOLS_DATA_FLOAT: + rc = float_cast(type, n); + break; + case SCOLS_DATA_BOOLEAN: + rc = bool_cast(type, n); + break; + default: + rc = -EINVAL; + break; + } + + if (rc == 0) + n->type = type; + + if (rc) + DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc)); + return rc; +} + +int filter_cast_param(struct libscols_filter *fltr, + struct libscols_line *ln, + int type, + struct filter_param *n, + struct filter_param **result) +{ + int rc; + int orgtype = n->type; + + DBG(FPARAM, ul_debugobj(n, "casting param to %s", datatype2str(type))); + rc = fetch_holder_data(fltr, n, ln); + if (rc) + return rc; + + if (type == orgtype) { + filter_ref_node((struct filter_node *) n); /* caller wants to call filter_unref_node() for the result */ + *result = n; + return 0; + } + + *result = copy_param(n); + if (!*result) + return -ENOMEM; + rc = cast_param(type, *result); + + DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc)); + return rc; +} + +int filter_next_param(struct libscols_filter *fltr, + struct libscols_iter *itr, struct filter_param **prm) +{ + int rc = 1; + + if (!fltr || !itr || !prm) + return -EINVAL; + *prm = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &fltr->params); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *prm, struct filter_param, pr_params); + rc = 0; + } + + return rc; +} + +/** + * scols_filter_assign_column: + * @fltr: pointer to filter + * @itr: iterator + * @name: holder name + * @col: column + * + * Assign @col to filter parametr. The parametr is addressed by @itr or by + * @name. See scols_filter_next_holder(). + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.40 + */ +int scols_filter_assign_column(struct libscols_filter *fltr, + struct libscols_iter *itr, + const char *name, struct libscols_column *col) +{ + struct filter_param *n = NULL; + + if (itr && itr->p) { + struct list_head *p = IS_ITER_FORWARD(itr) ? + itr->p->prev : itr->p->next; + n = list_entry(p, struct filter_param, pr_params); + } else if (name) { + struct libscols_iter xitr; + struct filter_param *x = NULL; + + scols_reset_iter(&xitr, SCOLS_ITER_FORWARD); + while (filter_next_param(fltr, &xitr, &x) == 0) { + if (x->col + || x->holder != F_HOLDER_COLUMN + || strcmp(name, x->holder_name) != 0) + continue; + n = x; + break; + } + } + + if (n) { + if (n->col) + scols_unref_column(n->col); + + DBG(FPARAM, ul_debugobj(n, "assing %s to column %s", name, + scols_column_get_name(col))); + n->col = col; + scols_ref_column(col); + } + + return n ? 0 : -EINVAL; +} + +/** + * scols_filter_next_holder: + * @fltr: filter instance + * @itr: a pointer to a struct libscols_iter instance + * @name: returns the next column name + * @type: 0 (not implemented yet) + * + * Finds the next holder used in the expression and and returns a name via + * @name. The currently supported holder type is only column name. + * + * Returns: 0, a negative value in case of an error, and 1 at the end. + * + * Since: 2.40 + */ +int scols_filter_next_holder(struct libscols_filter *fltr, + struct libscols_iter *itr, + const char **name, + int type) +{ + struct filter_param *prm = NULL; + int rc = 0; + + *name = NULL; + if (!type) + type = F_HOLDER_COLUMN; /* default */ + + do { + rc = filter_next_param(fltr, itr, &prm); + if (rc == 0 && (int) prm->holder == type) { + *name = prm->holder_name; + } + } while (rc == 0 && !*name); + + return rc; +} |