]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libsmartcols/src/filter.c
600ed0117bbccf191f7d506da08bcf3aab685197
[thirdparty/util-linux.git] / libsmartcols / src / filter.c
1 /*
2 * filter.c - functions for lines filtering
3 *
4 * Copyright (C) 2023 Karel Zak <kzak@redhat.com>
5 *
6 * This file may be redistributed under the terms of the
7 * GNU Lesser General Public License.
8 */
9
10 /**
11 * SECTION: filter
12 * @title: Filters and counters
13 * @short_description: defines lines filter and counter
14 *
15 * An API to define and use filter and counters.
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include "smartcolsP.h"
24
25 #include "filter-parser.h"
26 #include "filter-scanner.h"
27
28 /**
29 * scols_new_filter:
30 * @str: filter expression or NULL
31 *
32 * Allocated and optionally parses a new filter.
33 *
34 * Returns: new filter instance or NULL in case of error.
35 *
36 * Since: 2.40
37 */
38 struct libscols_filter *scols_new_filter(const char *str)
39 {
40 struct libscols_filter *fltr = calloc(1, sizeof(*fltr));
41
42 if (!fltr)
43 return NULL;
44
45 DBG(FLTR, ul_debugobj(fltr, "alloc"));
46 fltr->refcount = 1;
47 INIT_LIST_HEAD(&fltr->params);
48 INIT_LIST_HEAD(&fltr->counters);
49
50 if (str && scols_filter_parse_string(fltr, str) != 0) {
51 scols_unref_filter(fltr);
52 return NULL;
53 }
54
55 return fltr;
56 }
57
58 /**
59 * scols_ref_filter:
60 * @fltr: filter instance
61 *
62 * Increment filter reference counter.
63 *
64 * Since: 2.40
65 */
66 void scols_ref_filter(struct libscols_filter *fltr)
67 {
68 if (fltr)
69 fltr->refcount++;
70 }
71
72 static void reset_filter(struct libscols_filter *fltr)
73 {
74 if (!fltr)
75 return;
76 filter_unref_node(fltr->root);
77 fltr->root = NULL;
78
79 if (fltr->src)
80 fclose(fltr->src);
81 fltr->src = NULL;
82
83 free(fltr->errmsg);
84 fltr->errmsg = NULL;
85 }
86
87 static void remove_counters(struct libscols_filter *fltr)
88 {
89 if (!fltr)
90 return;
91
92 DBG(FLTR, ul_debugobj(fltr, "remove all counters"));
93 while (!list_empty(&fltr->counters)) {
94 struct libscols_counter *ct = list_entry(fltr->counters.next,
95 struct libscols_counter, counters);
96
97 filter_unref_node((struct filter_node *) ct->param);
98 list_del_init(&ct->counters);
99 free(ct->name);
100 free(ct);
101 }
102 }
103
104 /**
105 * scols_unref_filter:
106 * @fltr: filter instance
107 *
108 * Deincrements reference counter, unallocates the filter for the last
109 * reference.
110 *
111 * Since: 2.40
112 */
113 void scols_unref_filter(struct libscols_filter *fltr)
114 {
115 if (fltr && --fltr->refcount <= 0) {
116 DBG(FLTR, ul_debugobj(fltr, "dealloc"));
117 reset_filter(fltr);
118 remove_counters(fltr);
119 free(fltr);
120 }
121 }
122
123 /* This is generic allocater for a new node, always use the node type specific
124 * functions (e.g. filter_new_param() */
125 struct filter_node *__filter_new_node(enum filter_ntype type, size_t sz)
126 {
127 struct filter_node *n = calloc(1, sz);
128
129 if (!n)
130 return NULL;
131
132 n->type = type;
133 n->refcount = 1;
134 return n;
135 }
136
137 void filter_unref_node(struct filter_node *n)
138 {
139 if (!n || --n->refcount > 0)
140 return;
141
142 switch (n->type) {
143 case F_NODE_EXPR:
144 filter_free_expr((struct filter_expr *) n);
145 break;
146 case F_NODE_PARAM:
147 filter_free_param((struct filter_param *) n);
148 break;
149 }
150 }
151
152 void filter_ref_node(struct filter_node *n)
153 {
154 if (n)
155 n->refcount++;
156 }
157
158 void filter_dump_node(struct ul_jsonwrt *json, struct filter_node *n)
159 {
160 if (!n)
161 return;
162
163 switch (n->type) {
164 case F_NODE_EXPR:
165 filter_dump_expr(json, (struct filter_expr *) n);
166 break;
167 case F_NODE_PARAM:
168 filter_dump_param(json, (struct filter_param *) n);
169 break;
170 }
171 }
172
173
174
175 extern int yyparse(void *scanner, struct libscols_filter *fltr);
176
177 /**
178 * scols_filter_parse_string:
179 * @fltr: filter instance
180 * @str: string with filter expression
181 *
182 * Parses filter, see scols_filter_get_errmsg() for errors.
183 *
184 * Returns: 0, a negative number in case of an error.
185 *
186 * Since: 2.40
187 */
188 int scols_filter_parse_string(struct libscols_filter *fltr, const char *str)
189 {
190 yyscan_t sc;
191 int rc;
192
193 reset_filter(fltr);
194
195 if (!str || !*str)
196 return 0; /* empty filter is not error */
197
198 fltr->src = fmemopen((void *) str, strlen(str), "r");
199 if (!fltr->src)
200 return -errno;
201
202 yylex_init(&sc);
203 yyset_in(fltr->src, sc);
204
205 rc = yyparse(sc, fltr);
206 yylex_destroy(sc);
207
208 fclose(fltr->src);
209 fltr->src = NULL;
210
211 ON_DBG(FLTR, scols_dump_filter(fltr, stderr));
212
213 return rc;
214 }
215
216 /**
217 * scols_dump_filter:
218 * @fltr: filter instance
219 * @out: output stream
220 *
221 * Dumps internal filter nodes in JSON format. This function is mostly designed
222 * for debugging purpose. The fileds in the output are subject to change.
223 *
224 * Returns: 0, a negative number in case of an error.
225 *
226 * Since: 2.40
227 */
228 int scols_dump_filter(struct libscols_filter *fltr, FILE *out)
229 {
230 struct ul_jsonwrt json;
231
232 if (!fltr || !out)
233 return -EINVAL;
234
235 ul_jsonwrt_init(&json, out, 0);
236 ul_jsonwrt_root_open(&json);
237
238 filter_dump_node(&json, fltr->root);
239 ul_jsonwrt_root_close(&json);
240 return 0;
241 }
242
243 /**
244 * scols_filter_get_errmsg:
245 * @fltr: filter instance
246 *
247 * Returns: string with parse-error message of NULL (if no error)
248 *
249 * Since: 2.40
250 */
251 const char *scols_filter_get_errmsg(struct libscols_filter *fltr)
252 {
253 return fltr ? fltr->errmsg : NULL;
254 }
255
256 int filter_eval_node(struct libscols_filter *fltr, struct libscols_line *ln,
257 struct filter_node *n, int *status)
258 {
259 switch (n->type) {
260 case F_NODE_PARAM:
261 return filter_eval_param(fltr, ln, (struct filter_param *) n, status);
262 case F_NODE_EXPR:
263 return filter_eval_expr(fltr, ln, (struct filter_expr *) n, status);
264 default:
265 break;
266 }
267 return -EINVAL;
268 }
269
270 /**
271 * scols_line_apply_filter:
272 * @ln: apply filter to the line
273 * @fltr: filter instance
274 * @status: return 1 or 0 as result of the expression
275 *
276 * Applies filter (and also counters assisiated with the filter).
277 *
278 * Returns: 0, a negative number in case of an error.
279 *
280 * Since: 2.40
281 */
282 int scols_line_apply_filter(struct libscols_line *ln,
283 struct libscols_filter *fltr, int *status)
284 {
285 int rc, res = 0;
286 struct libscols_iter itr;
287 struct filter_param *prm = NULL;
288
289 if (!ln || !fltr)
290 return -EINVAL;
291
292 /* reset column data and types stored in the filter */
293 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
294 while (filter_next_param(fltr, &itr, &prm) == 0) {
295 filter_param_reset_holder(prm);
296 }
297
298 if (fltr->root)
299 rc = filter_eval_node(fltr, ln, fltr->root, &res);
300 else
301 rc = 0, res = 1; /* empty filter matches all lines */
302
303 if (rc == 0) {
304 struct libscols_counter *ct = NULL;
305
306 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
307 while (scols_filter_next_counter(fltr, &itr, &ct) == 0) {
308 if ((ct->neg && res == 0) || res == 1)
309 filter_count_param(fltr, ln, ct);
310 }
311 }
312
313 if (status)
314 *status = res;
315 DBG(FLTR, ul_debugobj(fltr, "filter done [rc=%d, status=%d]", rc, res));
316 return rc;
317 }
318
319 /**
320 * scols_filter_set_filler_cb:
321 * @fltr: filter instance
322 * @cb: application defined callback
323 * @userdata: pointer to private callback data
324 *
325 * The application can apply filter for empty lines to avoid filling the table
326 * with unnecessary data (for example if the line will be later removed from
327 * the table due to filter).
328 *
329 * This callback is used by filter to ask application to fill to the line data
330 * which are necessary to evaluate the filter expression. The callback
331 * arguments are filter, column number and userdata.
332 *
333 * <informalexample>
334 * <programlisting>
335 * ln = scols_table_new_line(tab, NULL);
336 *
337 * scols_filter_set_filler_cb(filter, my_filler, NULL);
338 *
339 * scols_line_apply_filter(line, filter, &status);
340 * if (status == 0)
341 * scols_table_remove_line(tab, line);
342 * else for (i = 0; i < ncolumns; i++) {
343 * if (scols_line_is_filled(line, i))
344 * continue;
345 * my_filler(NULL, ln, i, NULL);
346 * }
347 * </programlisting>
348 * </informalexample>
349 *
350 * Returns: 0, a negative number in case of an error.
351 *
352 * Since: 2.40
353 */
354 int scols_filter_set_filler_cb(struct libscols_filter *fltr,
355 int (*cb)(struct libscols_filter *,
356 struct libscols_line *, size_t, void *),
357 void *userdata)
358 {
359 if (!fltr)
360 return -EINVAL;
361 fltr->filler_cb = cb;
362 fltr->filler_data = userdata;
363
364 return 0;
365 }
366
367 /**
368 * scols_filter_new_counter:
369 * @fltr: filter instance
370 *
371 * Alocates a new counter instance into the filter.
372 *
373 * Returns: new counter or NULL in case of an error.
374 *
375 * Since: 2.40
376 */
377 struct libscols_counter *scols_filter_new_counter(struct libscols_filter *fltr)
378 {
379 struct libscols_counter *ct;
380
381 if (!fltr)
382 return NULL;
383
384 ct = calloc(1, sizeof(*ct));
385 if (!ct)
386 return NULL;
387
388 DBG(FLTR, ul_debugobj(fltr, "alloc counter"));
389
390 ct->filter = fltr; /* don't use ref.counting here */
391 INIT_LIST_HEAD(&ct->counters);
392 list_add_tail(&ct->counters, &fltr->counters);
393
394
395 return ct;
396 }
397
398 /**
399 * scols_counter_set_name:
400 * @ct: counter instance
401 * @name: something for humans
402 *
403 * The name is not use by library, it's just description usable for application
404 * when prints results from countes.
405 *
406 * Returns: 0, a negative number in case of an error.
407 *
408 * Since: 2.40
409 */
410 int scols_counter_set_name(struct libscols_counter *ct, const char *name)
411 {
412 if (!ct)
413 return -EINVAL;
414 return strdup_to_struct_member(ct, name, name);
415 }
416
417 /**
418 * scols_counter_set_param:
419 * @ct: counter instance
420 * @name: holder (column) name
421 *
422 * Assigns a counter to the column. The name is used in the same way as names
423 * in the filter expression. This is usable for counter that calcuate with data
424 * from table cells (e.g. max, sum, etc.)
425 *
426 * Returns: 0, a negative number in case of an error.
427 *
428 * Since: 2.40
429 */
430 int scols_counter_set_param(struct libscols_counter *ct, const char *name)
431 {
432 if (!ct)
433 return -EINVAL;
434
435 if (ct->param) {
436 filter_unref_node((struct filter_node *) ct->param);
437 ct->param = NULL;
438 }
439 if (name) {
440 ct->param = (struct filter_param *)
441 filter_new_param(ct->filter, SCOLS_DATA_U64,
442 F_HOLDER_COLUMN, (void *) name);
443 if (!ct->param)
444 return -ENOMEM;
445 }
446 return 0;
447 }
448
449 /**
450 * scols_counter_set_func:
451 * @ct: counter instance
452 * @func: SCOLS_COUNTER_{COUNT,MAX,MIN,SUM}
453 *
454 * Defines function to calculate data.
455 *
456 * Returns: 0, a negative number in case of an error.
457 *
458 * Since: 2.40
459 */
460 int scols_counter_set_func(struct libscols_counter *ct, int func)
461 {
462 if (!ct || func < 0 || func >= __SCOLS_NCOUNTES)
463 return -EINVAL;
464
465 ct->func = func;
466 return 0;
467 }
468
469 /**
470 * scols_counter_get_result:
471 * @ct: counter instance
472 *
473 * Returns: result from the counter
474 *
475 * Since: 2.40
476 */
477 unsigned long long scols_counter_get_result(struct libscols_counter *ct)
478 {
479 return ct ? ct->result : 0;
480 }
481
482 /**
483 * scols_counter_get_name:
484 * @ct: counter instance
485 *
486 * Returns: name of the counter.
487 *
488 * Since: 2.40
489 */
490 const char *scols_counter_get_name(struct libscols_counter *ct)
491 {
492 return ct ? ct->name : NULL;;
493 }
494
495 /**
496 * scols_filter_next_counter:
497 * @fltr: filter instance
498 * @itr: a pointer to a struct libscols_iter instance
499 * @ct: returns the next counter
500 *
501 * Finds the next counter and returns a pointer to it via @ct.
502 *
503 * Returns: 0, a negative value in case of an error, and 1 at the end.
504 *
505 * Since: 2.40
506 */
507 int scols_filter_next_counter(struct libscols_filter *fltr,
508 struct libscols_iter *itr, struct libscols_counter **ct)
509 {
510 int rc = 1;
511
512 if (!fltr || !itr || !ct)
513 return -EINVAL;
514 *ct = NULL;
515
516 if (!itr->head)
517 SCOLS_ITER_INIT(itr, &fltr->counters);
518 if (itr->p != itr->head) {
519 SCOLS_ITER_ITERATE(itr, *ct, struct libscols_counter, counters);
520 rc = 0;
521 }
522
523 return rc;
524 }