1 /* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /**
24 services: log filter: basic filtering
25
26 This implementation, "dragnet" is currently the default filter
27 and therefore built-in. Basic configuration is built into the
28 server proper (via log_error_verbosity etc.); for advanced configuration,
29 load the service log_filter_dragnet which implements a configuration
30 language for this engine. See there for details about the Configuration
31 Stage. Some of the code-paths are only available via the configuration
32 language or an equivalent service (but not without any such service loaded).
33
34 At present, the design is such that multiple threads can call the
35 filter concurrently; the ruleset is global and shared between all
36 users.
37
38
39 FILTERING STAGE
40
41 At run time, the filter iterates over its rule-set. For each
42 rule, if the condition contains a well-known item, it looks for
43 an item of that type in the event. If the condition contains
44 an ad hoc-item, it looks for an item of any ad hoc-type with
45 the given key within the event.
46
47 If there is a match, the filter will verify whether the storage
48 class of the value in the event and that in the condition are
49 either both strings, or both not. If the classes do not match,
50 it flags an error. Otherwise, it now compares both values using
51 the requested comparator, and reports the result.
52
53 If a log event matches a rule, an action ("suppress log line",
54 "delete field", etc.) will be applied to that event.
55
56
57 LOCKING
58
59 During the filtering stage, a shared lock on the ruleset is held.
60 An exclusive lock on the ruleset is only taken as response to the
61 user's changing of the filter configuration, which should be rare.
62
63 For debugging puroposes, rules feature a counter of how often events
64 matched them; this counter is updated atomically.
65
66 Rate-limiting ("throttle") needs some bookkeeping data (when does
67 the current window expire? how many matches have we had so far
68 within the current window? etc.). A write-lock is taken on the
69 individual rule (not the entire ruleset) to update this information;
70 any throttling-related actions taken on the event happen after this
71 lock has been released.
72
73 The event itself is not locked.
74 */
75
76 #include <mysqld_error.h>
77
78 #include "log_builtins_filter_imp.h"
79 #include "log_builtins_imp.h"
80 #include "my_atomic.h"
81 #include "my_systime.h" // my_micro_time()
82 #include "mysys_err.h" // EE_ERROR_LAST for globerrs
83 #include "sql/derror.h"
84 #include "sql/log.h"
85 // for the default rules
86 #include "sql/mysqld.h"
87
88 #define THROTTLE_DEFAULT_WINDOW_SIZE_IN_SECONDS 60
89 #define THROTTLE_MICROSECOND_MULTIPLIER 1000000
90
91 static bool filter_inited = false;
92 static ulong filter_rule_uuid = 0;
93
94 log_filter_ruleset *log_filter_builtin_rules = nullptr;
95 log_filter_tag rule_tag_builtin = {"log_filter_builtin", nullptr};
96
97 /**
98 Predicate: can we add any more rules?
99
100 @param rs the ruleset to check
101
102 @retval true full, no more rules can be added
103 @retval false not full, further rules can be added
104 */
log_filter_ruleset_full(log_filter_ruleset * rs)105 static bool log_filter_ruleset_full(log_filter_ruleset *rs) {
106 return (rs->count >= LOG_FILTER_RULE_MAX);
107 }
108
109 /**
110 Initialize a new rule.
111
112 This clears the first unused rule. It does not update the rules
113 count; this is for the caller to do if it succeeds in setting up
114 the rule to its satisfaction. If the caller fails, it should
115 log_builtins_filter_rule_free() the incomplete rule.
116
117 @retval nullptr: could not initialize rule. Do not call rule_free.
118 @retval !nullptr: the address of the rule. fill in. on success,
119 caller must increase rule count. on failure,
120 it must call rule_free.
121 */
log_builtins_filter_rule_init(log_filter_ruleset * ruleset)122 static log_filter_rule *log_builtins_filter_rule_init(
123 log_filter_ruleset *ruleset) {
124 log_filter_rule *r = &ruleset->rule[ruleset->count];
125
126 memset(r, 0, sizeof(log_filter_rule));
127
128 r->id = ++filter_rule_uuid;
129 r->throttle_window_size =
130 THROTTLE_DEFAULT_WINDOW_SIZE_IN_SECONDS; // 1 minute
131
132 if (mysql_rwlock_init(0, &(r->rule_lock))) return nullptr;
133
134 return r;
135 }
136
137 /**
138 Release all resources associated with a filter rule.
139 Leaves a "gap" (an uninitialized rule) for immediate re-filling;
140 if this is undesired, use log_builtins_filter_rule_remove() (see there).
141 Must hold rule-set lock.
142
143 @param ri the rule to release
144
145 @retval the return value from mysql_rwlock_destroy()
146 */
log_builtins_filter_rule_free(log_filter_rule * ri)147 static int log_builtins_filter_rule_free(log_filter_rule *ri) {
148 ri->cond = LOG_FILTER_COND_NONE;
149 ri->verb = LOG_FILTER_UNDEF;
150
151 // release memory if needed
152 log_item_free(&(ri->match));
153 log_item_free(&(ri->aux));
154
155 return mysql_rwlock_destroy(&(ri->rule_lock));
156 }
157
158 /**
159 Release filter rule (key/value pair) with the index "elem" in "ruleset".
160 This frees whichever of key and value were dynamically allocated.
161 It then moves any trailing items to fill the "gap" and decreases the
162 counter of elements in the rule-set.
163
164 If the intention is to leave a "gap" in the bag that may immediately be
165 overwritten with an updated element, use log_builtins_filter_rule_free()
166 instead.
167
168 Caller must hold rule-set lock.
169
170 @param ruleset filter rule-set
171 @param elem index of the filter rule to release
172 */
log_builtins_filter_rule_remove(log_filter_ruleset * ruleset,int elem)173 static void log_builtins_filter_rule_remove(log_filter_ruleset *ruleset,
174 int elem) {
175 size_t rn;
176
177 DBUG_ASSERT(ruleset->count > 0);
178
179 log_builtins_filter_rule_free(&ruleset->rule[elem]);
180
181 for (rn = elem; rn < (ruleset->count - 1); rn++) {
182 ruleset->rule[rn] = ruleset->rule[rn + 1];
183 }
184
185 ruleset->count--;
186 }
187
188 /**
189 Create a new set of filter rules.
190
191 @param tag tag for this ruleset
192 @param count number of rules to allocate, 0 for default
193
194 @retval a pointer to a ruleset structure, or nullptr on failure
195 */
log_builtins_filter_ruleset_new(log_filter_tag * tag,size_t count)196 static log_filter_ruleset *log_builtins_filter_ruleset_new(log_filter_tag *tag,
197 size_t count) {
198 log_filter_ruleset *ruleset = nullptr;
199
200 if ((tag == nullptr) || (tag->filter_name == nullptr))
201 return nullptr; /* purecov: inspected */
202
203 ruleset =
204 (log_filter_ruleset *)my_malloc(0, sizeof(log_filter_ruleset), MYF(0));
205
206 if (ruleset != nullptr) {
207 memset(ruleset, 0, sizeof(log_filter_ruleset));
208 ruleset->tag = tag;
209 ruleset->alloc = (count < 1) ? LOG_FILTER_RULE_MAX : count;
210
211 if (mysql_rwlock_init(0, &ruleset->ruleset_lock)) {
212 my_free((void *)ruleset); /* purecov: inspected */
213 ruleset = nullptr; /* purecov: inspected */
214 }
215 }
216
217 return ruleset;
218 }
219
220 /**
221 Lock and get the filter rules.
222
223 @param ruleset the ruleset to lock
224 @param lt LOG_BUILTINS_LOCK_SHARED lock for reading
225 LOG_BUILTINS_LOCK_EXCLUSIVE lock for writing
226
227 @retval 0 lock acquired
228 @retval !0 failed to acquire lock
229 */
log_builtins_filter_ruleset_lock(log_filter_ruleset * ruleset,log_builtins_filter_lock lt)230 static int log_builtins_filter_ruleset_lock(log_filter_ruleset *ruleset,
231 log_builtins_filter_lock lt) {
232 if ((!filter_inited) || (ruleset == nullptr)) return -1;
233
234 switch (lt) {
235 case LOG_BUILTINS_LOCK_SHARED:
236 mysql_rwlock_rdlock(&ruleset->ruleset_lock);
237 break;
238 case LOG_BUILTINS_LOCK_EXCLUSIVE:
239 mysql_rwlock_wrlock(&ruleset->ruleset_lock);
240 break;
241 default:
242 return -2; /* purecov: inspected */
243 }
244
245 return 0;
246 }
247
248 /**
249 Drop an entire filter rule-set. Must hold lock.
250 */
log_builtins_filter_ruleset_drop(log_filter_ruleset * ruleset)251 static void log_builtins_filter_ruleset_drop(log_filter_ruleset *ruleset) {
252 log_filter_rule *ri;
253
254 while (ruleset->count > 0) {
255 ruleset->count--;
256 ri = &ruleset->rule[ruleset->count];
257 log_builtins_filter_rule_free(ri);
258 }
259 }
260
261 /**
262 Unlock filter ruleset.
263 */
log_builtins_filter_ruleset_unlock(log_filter_ruleset * ruleset)264 static void log_builtins_filter_ruleset_unlock(log_filter_ruleset *ruleset) {
265 if (ruleset != nullptr) {
266 mysql_rwlock_unlock(&(ruleset->ruleset_lock));
267 }
268 }
269
270 /**
271 Free filter ruleset.
272 */
log_builtins_filter_ruleset_free(log_filter_ruleset ** ruleset)273 static void log_builtins_filter_ruleset_free(log_filter_ruleset **ruleset) {
274 if (ruleset != nullptr) {
275 log_filter_ruleset *rs = *ruleset;
276 if (rs != nullptr) {
277 *ruleset = nullptr;
278
279 log_builtins_filter_ruleset_drop(rs);
280 log_builtins_filter_ruleset_unlock(rs);
281
282 mysql_rwlock_destroy(&(rs->ruleset_lock));
283
284 my_free((void *)rs);
285 }
286 }
287 }
288
289 /**
290 Defaults for when the configuration engine isn't loaded;
291 aim for 5.7 compatibilty.
292 */
log_builtins_filter_set_defaults(log_filter_ruleset * ruleset)293 static void log_builtins_filter_set_defaults(log_filter_ruleset *ruleset) {
294 log_filter_rule *r;
295
296 DBUG_ASSERT(ruleset != nullptr);
297
298 DBUG_ASSERT(!log_filter_ruleset_full(ruleset));
299
300 // failsafe: simple built-in filter may not drop "System" or "Error" messages
301 r = log_builtins_filter_rule_init(ruleset);
302 log_item_set_with_key(&r->match, LOG_ITEM_LOG_PRIO, nullptr,
303 LOG_ITEM_FREE_NONE)
304 ->data_integer = WARNING_LEVEL;
305 r->cond = LOG_FILTER_COND_LT;
306 r->verb = LOG_FILTER_RETURN;
307
308 ruleset->count++;
309
310 // sys_var: log_error_verbosity
311 r = log_builtins_filter_rule_init(ruleset);
312 log_item_set_with_key(&r->match, LOG_ITEM_LOG_PRIO, nullptr,
313 LOG_ITEM_FREE_NONE)
314 ->data_integer = log_error_verbosity;
315 r->cond = LOG_FILTER_COND_GT;
316 r->verb = LOG_FILTER_DROP;
317
318 ruleset->count++;
319
320 // example: remove all source-line log items
321 // these are not desirable by default, only while debugging.
322 r = log_builtins_filter_rule_init(ruleset);
323 log_item_set(&r->match, LOG_ITEM_SRC_LINE);
324 r->cond = LOG_FILTER_COND_PRESENT;
325 r->verb = LOG_FILTER_ITEM_DEL;
326 // aux optional
327
328 ruleset->count++;
329 }
330
331 /**
332 Deinitialize filtering engine.
333
334 @retval 0 Success!
335 @retval -1 De-initialize? Filter wasn't even initialized!
336 */
log_builtins_filter_exit()337 int log_builtins_filter_exit() {
338 if (!filter_inited) return -1;
339
340 /*
341 Nobody else should run at this point anyway, but
342 since we made a big song and dance about having
343 to hold this lock above ...
344 */
345 if (log_filter_builtin_rules != nullptr) {
346 mysql_rwlock_wrlock(&log_filter_builtin_rules->ruleset_lock);
347 filter_inited = false;
348 log_builtins_filter_ruleset_free(&log_filter_builtin_rules);
349 } else
350 filter_inited = false; /* purecov: inspected */
351
352 return 0;
353 }
354
355 /**
356 Initialize filtering engine.
357 We need to do this early, before the component system is up.
358
359 @retval 0 Success!
360 @retval -1 Couldn't initialize ruleset
361 @retval -2 Filter was already initialized?
362 */
log_builtins_filter_init()363 int log_builtins_filter_init() {
364 if (!filter_inited) {
365 log_filter_builtin_rules =
366 log_builtins_filter_ruleset_new(&rule_tag_builtin, 0);
367 if (log_filter_builtin_rules == nullptr) return -1;
368
369 log_builtins_filter_set_defaults(log_filter_builtin_rules);
370 filter_inited = true;
371
372 return 0;
373 } else
374 return -2;
375 }
376
377 /**
378 Apply the action of an individual rule to an individual log line (or
379 a part thereof, i.e. a "field"). At this point, we already know that
380 the current log line matches the condition.
381
382 @param[in,out] ll the current log line
383 @param ln index of the matching field,
384 -1 for none (when a test for absence
385 matched)
386 @param r the rule to apply. internal state may
387 be changed (i.e. the number of seen
388 matches for a throttle rule)
389
390 @retval log_filter_apply 0 on success, an error-code otherwise
391 */
392
log_filter_try_apply(log_line * ll,int ln,log_filter_rule * r)393 static log_filter_apply log_filter_try_apply(log_line *ll, int ln,
394 log_filter_rule *r) {
395 switch (r->verb) {
396 case LOG_FILTER_RETURN:
397 break;
398
399 case LOG_FILTER_DROP:
400 log_line_item_free_all(ll);
401 break;
402
403 case LOG_FILTER_THROTTLE: {
404 ulonglong now = my_micro_time();
405 ulong rate = (ulong)(
406 (r->aux.data.data_integer < 0) ? 0 : r->aux.data.data_integer);
407 ulong suppressed = 0;
408 ulong matches;
409
410 /*
411 Check whether we're still in the current window. (If not, we
412 will want to print a summary (if the logging of any lines was
413 suppressed) and start a new window.)
414 */
415 mysql_rwlock_wrlock(&(r->rule_lock));
416
417 if (now >= r->throttle_window_end) {
418 suppressed =
419 (r->throttle_matches > rate) ? (r->throttle_matches - rate) : 0;
420
421 // new window
422 r->throttle_matches = 0;
423 r->throttle_window_end =
424 now + (r->throttle_window_size * THROTTLE_MICROSECOND_MULTIPLIER);
425 }
426
427 matches = ++r->throttle_matches;
428
429 mysql_rwlock_unlock(&(r->rule_lock));
430
431 /*
432 If it's over the limit for the current window, discard the whole
433 log line. A rate of 0 ("discard all") is allowed for convenience,
434 but the DROP verb should be used instead.
435 */
436 if (matches > rate) {
437 log_line_item_free_all(ll);
438 }
439
440 // if we actually suppressed any lines, add info declaring that
441 else if ((suppressed > 0) && !log_line_full(ll)) {
442 log_line_item_set(ll, LOG_ITEM_LOG_SUPPRESSED)->data_integer =
443 suppressed;
444 }
445 } break;
446
447 case LOG_FILTER_ITEM_SET:
448 if (r->aux.key == nullptr) return LOG_FILTER_APPLY_TARGET_NOT_IN_LOG_LINE;
449
450 // if not same field as in match, look for it
451 if ((ln < 0) || (0 != native_strcasecmp(r->aux.key, ll->item[ln].key)))
452 ln = log_line_index_by_item(ll, &r->aux);
453
454 if (ln >= 0) // found? release for over-write!
455 log_item_free(&ll->item[ln]);
456 else if (log_line_full(ll)) // otherwise, add it if there's still space
457 return LOG_FILTER_APPLY_OUT_OF_MEMORY;
458 else
459 ln = ll->count++;
460
461 ll->item[ln] = r->aux;
462 /*
463 It's a shallow copy, don't try to free it.
464 As a downside, the filter rules must remain shared-locked until
465 the line is logged. The assumption is that logging happens vastly
466 more than changing of the ruleset.
467 */
468 ll->item[ln].alloc = LOG_ITEM_FREE_NONE;
469 ll->seen |= r->aux.type;
470 break;
471
472 case LOG_FILTER_ITEM_DEL:
473 // might want to delete a field other than that from the cond:
474 if ((r->aux.key != nullptr) &&
475 ((ln < 0) ||
476 (0 != native_strcasecmp(r->aux.key, ll->item[ln].key)))) {
477 ln = log_line_index_by_item(ll, &r->aux);
478 }
479
480 if (ln < 0) return LOG_FILTER_APPLY_TARGET_NOT_IN_LOG_LINE;
481
482 {
483 log_item_type t = ll->item[ln].type;
484
485 log_line_item_remove(ll, ln);
486
487 /*
488 If it's a well-known type (and therefore unique), or if it's
489 the last one of a generic type, unflag the presence of that type.
490 */
491 if (!log_item_generic_type(t) || (log_line_index_by_type(ll, t) < 0))
492 ll->seen &= ~t;
493 }
494
495 break;
496
497 default:
498 return LOG_FILTER_APPLY_UNKNOWN_OPERATION;
499 }
500
501 return LOG_FILTER_APPLY_SUCCESS;
502 }
503
504 /**
505 Try to match an individual log line-field against an individual
506 rule's condition
507
508 @param li the log item we try to match
509 @param ri the rule containing the condition
510
511 @retval log_filter_match 0 (LOG_FILTER_MATCH_SUCCESS) on match,
512 1 (LOG_FILTER_MATCH_UNSATISFIED) or
513 an error-code otherwise
514 */
log_filter_try_match(log_item * li,log_filter_rule * ri)515 static log_filter_match log_filter_try_match(log_item *li,
516 log_filter_rule *ri) {
517 bool rc, lc;
518 log_filter_match e = LOG_FILTER_MATCH_UNCOMPARED;
519
520 /*
521 If a condition is e.g. prio > 4,
522 - the "4" is part of the filter-rule (rule-item "ri")
523 - the "prio" will be substitute with that field's value
524 in the log-event (the corresponding log-item, "li")
525
526 I.e. we're lucky in that as mnemonics go, the "l" in
527 "lc", "lf", "li" can stand for both "log-event" and
528 "left in the comparison", and as can the "r" for both
529 "rule" and "right in the comparison."
530 */
531
532 DBUG_ASSERT(ri != nullptr);
533
534 /*
535 If there is no match, the only valid scenarios are "success"
536 (if we tested for absence), or failure (otherwise). Handle
537 them here to make any derefs of li beyond this point safe.
538 */
539 if (li == nullptr)
540 return (ri->cond == LOG_FILTER_COND_ABSENT) ? LOG_FILTER_MATCH_SUCCESS
541 : LOG_FILTER_MATCH_UNSATISFIED;
542
543 else if (ri->cond == LOG_FILTER_COND_PRESENT)
544 return LOG_FILTER_MATCH_SUCCESS;
545
546 else if (ri->cond == LOG_FILTER_COND_ABSENT)
547 return LOG_FILTER_MATCH_UNSATISFIED;
548
549 // item class on left hand side / right hand side
550 rc = log_item_string_class(ri->match.item_class);
551 lc = log_item_string_class(li->item_class);
552
553 // if one's a string and the other isn't, fail for now
554 if (rc != lc)
555 e = LOG_FILTER_MATCH_CLASSES_DIFFER;
556
557 else {
558 e = LOG_FILTER_MATCH_UNSATISFIED;
559 double rf, lf;
560
561 // we're comparing two strings
562 if (rc) {
563 /*
564 We're setting up lf to be the result of our string-comparison
565 (<0, 0, >0), and rf to be 0. This allows us to use all the
566 numerical comparators below that compare lf and rf.
567 */
568
569 rf = 0;
570
571 // some ado in case the strings aren't \0 terminated
572 size_t len; // length of the shorter string
573 len = (ri->match.data.data_string.length < li->data.data_string.length)
574 ? ri->match.data.data_string.length
575 : li->data.data_string.length;
576
577 // compare for the shorter of the given lengths
578 lf = log_string_compare(li->data.data_string.str,
579 ri->match.data.data_string.str, len,
580 false); // case-sensitive
581
582 /*
583 If strings are the same for the shared length,
584 but are of different length, longer string "wins"
585 */
586 if ((lf == 0) &&
587 (ri->match.data.data_string.length != li->data.data_string.length))
588 lf = (li->data.data_string.length > len) ? 1 : -1;
589 }
590
591 // we're comparing numerically
592 else {
593 // get values as floats (works for integer items as well)
594 log_item_get_float(&ri->match, &rf);
595 log_item_get_float(li, &lf);
596 }
597
598 // do the actual comparison
599 switch (ri->cond) {
600 case LOG_FILTER_COND_EQ: // == Equal
601 if (lf == rf) e = LOG_FILTER_MATCH_SUCCESS;
602 break;
603
604 case LOG_FILTER_COND_NE: // != Not Equal
605 if (lf != rf) e = LOG_FILTER_MATCH_SUCCESS;
606 break;
607
608 case LOG_FILTER_COND_GE: // >= Greater or Equal
609 if (lf >= rf) e = LOG_FILTER_MATCH_SUCCESS;
610 break;
611
612 case LOG_FILTER_COND_LT: // < Less Than
613 if (lf < rf) e = LOG_FILTER_MATCH_SUCCESS;
614 break;
615
616 case LOG_FILTER_COND_LE: // <= Less or Equal
617 if (lf <= rf) e = LOG_FILTER_MATCH_SUCCESS;
618 break;
619
620 case LOG_FILTER_COND_GT: // > Greater than
621 if (lf > rf) e = LOG_FILTER_MATCH_SUCCESS;
622 break;
623
624 // unknown comparison type
625 default:
626 e = LOG_FILTER_MATCH_COMPARATOR_UNKNOWN; /* purecov: inspected */
627 DBUG_ASSERT(false);
628 } // comparator switch
629 } // class mismatch?
630
631 return e;
632 }
633
634 /**
635 Apply all matching rules from a filter rule set to a given log line.
636
637 @param ruleset rule-set to apply
638 @param ll the current log line
639
640 @retval int number of matched rules
641 */
log_builtins_filter_run(log_filter_ruleset * ruleset,log_line * ll)642 int log_builtins_filter_run(log_filter_ruleset *ruleset, log_line *ll) {
643 size_t rn; // rule number
644 int ln = -1; // log-item number
645 log_filter_rule *r; // current rule
646 int processed = 0;
647 log_filter_match cond_result;
648
649 DBUG_ASSERT(filter_inited);
650
651 if (ruleset == nullptr) return 0; /* purecov: inspected */
652
653 mysql_rwlock_rdlock(&ruleset->ruleset_lock);
654
655 for (rn = 0; ((rn < ruleset->count) && (ll->seen != LOG_ITEM_END)); rn++) {
656 r = &ruleset->rule[rn];
657
658 /*
659 If the rule is temporarily disabled, skip over it.
660 If and when LOG_FILTER_CHAIN_AND/LOG_FILTER_CHAIN_OR
661 are added, those chained conditions must be muted/unmuted
662 along with the first one, i.e. as a group.
663 */
664 if (r->flags & LOG_FILTER_FLAG_DISABLED) continue;
665
666 /*
667 Look for a matching field in the event!
668 */
669
670 /*
671 Currently applies to 0 or 1 match, there is no multi-match.
672 */
673
674 if (r->cond == LOG_FILTER_COND_NONE) // in ELSE etc. there is no condition
675 {
676 /*
677 We could just initialize this at a top and then chain this
678 (i.e. rules with conditions will overwrite this; while rules
679 without (i.e. ELSE) will "inherit" the previous value, but we
680 don't currently have to as implicit-DELETE-field is set up at
681 parse-time, not at execution time. For that reason, we always
682 reset this for the time being so people will find the code
683 easier to understand.
684 */
685 ln = -1;
686 goto apply_action;
687 }
688
689 ln = log_line_index_by_item(ll, &r->match);
690
691 /*
692 If we found a suitable field, see whether its value satisfies
693 the condition given in the rule. If so, apply the action.
694
695 ln == -1 would indicate "not found", which we can actually
696 match against for cases like, "if one doesn't exist, create one now."
697 */
698 cond_result = log_filter_try_match((ln >= 0) ? &ll->item[ln] : nullptr, r);
699
700 if (cond_result == LOG_FILTER_MATCH_SUCCESS) {
701 /*
702 Protect match_count in case of non-atomic updates.
703 */
704 mysql_rwlock_wrlock(&(r->rule_lock));
705 ++r->match_count;
706 mysql_rwlock_unlock(&(r->rule_lock));
707
708 if (r->verb == LOG_FILTER_CHAIN_AND) // AND -- test next condition
709 continue; // proceed with next cond
710
711 else if (r->verb == LOG_FILTER_CHAIN_OR) // OR -- one match is enough
712 { // skip any other conditions in OR
713 while (ruleset->rule[rn].verb == LOG_FILTER_CHAIN_OR) rn++;
714 r = &ruleset->rule[rn];
715 }
716 /*
717 If we're here, we either had a single-condition IF match,
718 or one condition in an OR-chain matched.
719 In either case, it's time to apply the verb now.
720 */
721 } else if (cond_result == LOG_FILTER_MATCH_UNSATISFIED) {
722 if (r->verb ==
723 LOG_FILTER_CHAIN_AND) { // jump to next branch (ELSEIF/ELSE/ENDIF)
724 while (ruleset->rule[++rn].verb == LOG_FILTER_CHAIN_AND)
725 ; // skip over all AND conditions
726 }
727 /*
728 skip over rule:
729 - if this was an AND-chain, it'll skip over last cond and the action
730 - if this was an OR-chain, it'll proceed to the next condition
731 - if this was a solitary IF, it'll skip the action
732 */
733 continue; // skip over last condition (the one with the actual action)
734 } else // misc. failures (type mismatch etc.)
735 continue;
736
737 apply_action:
738 if (log_filter_try_apply(ll, ln, r) == LOG_FILTER_APPLY_SUCCESS) {
739 processed++;
740
741 if (r->verb == LOG_FILTER_RETURN) goto done;
742 }
743
744 if (r->jump != 0) // we're at the end of a block; jump to ENDIF
745 rn += r->jump - 1;
746 }
747
748 done:
749 mysql_rwlock_unlock(&ruleset->ruleset_lock);
750
751 return processed;
752 }
753
754 /**
755 This is part of the 5.7 emulation:
756 If --log_error_verbosity is changed, we generate an
757 artificial filter rule from it here.
758
759 For this filtering to be active, @@global.log_error_services
760 has to feature "log_filter_internal", as it does by default.
761 When that is the case, one or both of log_error_verbosity and
762 log_error_suppression_list (see below) may be used.
763 Only one of "log_filter_internal" and "log_filter_dragnet"
764 should be used at a time.
765
766 @param verbosity log_error_verbosity style, range(1,3)
767 1:errors, 2:+=warnings, 3:+=notes
768
769 @retval 0: success
770 @retval !0: failure
771 */
log_builtins_filter_update_verbosity(int verbosity)772 int log_builtins_filter_update_verbosity(int verbosity) {
773 size_t rn;
774 log_filter_rule *r;
775 int rr = -99;
776
777 if (log_builtins_filter_ruleset_lock(log_filter_builtin_rules,
778 LOG_BUILTINS_LOCK_EXCLUSIVE) < 0)
779 return -1;
780
781 /*
782 If a log_error_verbosity item already exists, update it.
783 This should always be the case now since we create an item on start-up
784 and different filter components can keep their rule-sets separate.
785 */
786 for (rn = 0; (rn < log_filter_builtin_rules->count); rn++) {
787 r = &log_filter_builtin_rules->rule[rn];
788
789 if ((r->match.type == LOG_ITEM_LOG_PRIO) && (r->verb == LOG_FILTER_DROP) &&
790 (r->cond == LOG_FILTER_COND_GT)) {
791 r->match.data.data_integer = verbosity;
792 r->flags &= ~LOG_FILTER_FLAG_DISABLED;
793 rr = 0;
794 goto done;
795 }
796 }
797
798 /* purecov: begin deadcode */
799
800 // if no log_error_verbosity item already exists, create one
801 if (log_filter_ruleset_full(
802 log_filter_builtin_rules)) { /* since this is a private rule-set, this
803 should never happen */
804 rr = -2;
805 goto done;
806 }
807
808 r = log_builtins_filter_rule_init(log_filter_builtin_rules);
809
810 log_item_set_with_key(&r->match, LOG_ITEM_LOG_PRIO, nullptr,
811 LOG_ITEM_FREE_NONE)
812 ->data_integer = verbosity;
813 r->cond = LOG_FILTER_COND_GT;
814 r->verb = LOG_FILTER_DROP;
815
816 log_filter_builtin_rules->count++;
817
818 rr = 1; /* purecov: end */
819
820 done:
821 log_builtins_filter_ruleset_unlock(log_filter_builtin_rules);
822
823 return rr;
824 }
825
826 /**
827 @@global.log_error_suppression_list accepts a comma-separated
828 list of error-codes that should not be included in the error-log.
829 Events with a severity of System or Error can not be filtered
830 in this way and will always be forwarded to the log-sinks.
831
832 This provides simple filtering for cases where the flexibility
833 of the loadable filter-language is not needed. (The same engine
834 is used however, just with a more limited interface.)
835
836 For this filtering to be active, @@global.log_error_services has
837 to feature "log_filter_internal", as it does by default. When that
838 is the case, one or both of log_error_verbosity and this variable
839 may be used. Only one of "log_filter_internal" and "log_filter_dragnet"
840 should be used at a time.
841
842 The semantics follow that of our system variables; that is to say,
843 when called with update==false, the function acts as a check-function
844 that validates the entire list given to it; when called with
845 update==true, it creates filter-rules from the list items. This way,
846 we either create all rules, or no rules, rather than ending up with
847 an incomplete rule-set when we encounter a problem in the input.
848
849 The return value encodes the location in the argument where the
850 failure occurred, like so:
851 - if 0 is returned, no issues were detected
852 - if a value less than zero is returned, -(retval + 1) is the
853 byte position (counting from 0) in the list argument at
854 which point the failure was detected
855
856 @param list list of error-codes that should not appear
857 in the error-log
858 @param update false: verify list only
859 true: create filtering rules from suppression list
860
861 @retval 0: success
862 @retval !0: failure (see above)
863 */
log_builtins_filter_parse_suppression_list(char * list,bool update)864 int log_builtins_filter_parse_suppression_list(char *list, bool update) {
865 char symbol[80]; // error to suppress ("1234" or "MY-001234")
866 char *start = list; // start of token
867 char *end = nullptr; // end of token
868 size_t len; // length of token
869 int errcode; // error-code of token
870 int commas = -1; // reject two separators in a row
871 int rr = 0; // return value
872 size_t rn; // rule number
873 log_filter_rule *r; // rule
874 uint list_len = 0; // number of error-codes in input
875
876 // in update-mode, discard old suppress rules for filter rule-set
877 if (update) {
878 // lock rule-set
879 if (log_builtins_filter_ruleset_lock(log_filter_builtin_rules,
880 LOG_BUILTINS_LOCK_EXCLUSIVE) < 0)
881 return -1; // bail directly (without trying to unlock)
882
883 for (rn = 0; (rn < log_filter_builtin_rules->count);) {
884 r = &log_filter_builtin_rules->rule[rn];
885
886 if ((r->match.type == LOG_ITEM_SQL_ERRCODE) &&
887 (r->verb == LOG_FILTER_DROP) &&
888 (r->cond == LOG_FILTER_COND_EQ)) // match "drop by errcode"
889 log_builtins_filter_rule_remove(log_filter_builtin_rules, rn);
890 else
891 rn++;
892 }
893 }
894
895 if (list == nullptr) goto success;
896
897 while (true) {
898 // find start of token
899 while ((*start != '\0') && (isspace(*start) || (*start == ','))) {
900 if (*start == ',') {
901 /*
902 commas at this point:
903 -1 We're looking for the first element. No comma allowed yet!
904 0 We're looking for element 2+, no comma yet, one is allowed.
905 1 We're looking for element 2+ and already have a comma!
906 */
907 if (commas != 0) goto fail;
908 commas++;
909 }
910 start++;
911 }
912 end = start;
913
914 // find first non-token character (i.e. first character after token)
915 while ((*end != '\0') && !(isspace(*end) || (*end == ','))) end++;
916
917 // no token found, end loop
918 if ((len = (end - start)) == 0) {
919 if (commas < 1) // list may not end in a comma
920 goto success;
921 goto fail;
922 }
923
924 if (commas == 0) // element needs to be first, or follow a comma
925 goto fail;
926 else
927 commas = 0;
928
929 // token too long, error
930 if (len >= sizeof(symbol) - 1) goto fail;
931 strncpy(symbol, start, len);
932 symbol[len] = '\0';
933
934 // numeric token "1234"
935 if (isdigit(symbol[0])) {
936 char *last;
937 errcode = (int)strtol(symbol, &last, 10);
938 if (*last != '\0') errcode = -1;
939 }
940 // alphanumeric token ("MY-1234" or "ER_FOO")
941 else
942 errcode = mysql_symbol_to_errno(symbol);
943
944 if (!(((errcode >= EE_ERROR_FIRST) && (errcode <= EE_ERROR_LAST)) ||
945 ((errcode >= ER_SERVER_RANGE_START) &&
946 (mysql_errno_to_builtin(errcode) >= 0))))
947 goto fail;
948
949 if (update) {
950 // make sure we have the space
951 if (log_filter_ruleset_full(log_filter_builtin_rules)) goto fail;
952
953 /*
954 This would require the mutex-init to fail,
955 which we can't know beforehand.
956 */
957 if ((r = log_builtins_filter_rule_init(log_filter_builtin_rules)) ==
958 nullptr)
959 goto fail; /* purecov: inspected */
960
961 log_item_set_with_key(&r->match, LOG_ITEM_SQL_ERRCODE, nullptr,
962 LOG_ITEM_FREE_NONE)
963 ->data_integer = errcode;
964 r->cond = LOG_FILTER_COND_EQ;
965 r->verb = LOG_FILTER_DROP;
966
967 log_filter_builtin_rules->count++;
968 }
969
970 /*
971 During check-phase, make sure the requested number of error-codes
972 (and therefore, the requested number of DROP rules) will fit into
973 the built-in rule-set. Reserve one rule for --log-error-verbosity
974 and one for our "ERROR and SYSTEM always pass" shortcut.
975 Without this check, we'd still catch the (attempted) overflow during
976 assignment, but if we do it during the check phase, we protect the
977 integrity of both the current rule-set and the variable's value.
978 */
979 else if (++list_len >= (log_filter_builtin_rules->alloc - 2))
980 goto fail;
981
982 start = end;
983 }
984
985 fail:
986 rr = -(start - list + 1);
987 success:
988 if (update) log_builtins_filter_ruleset_unlock(log_filter_builtin_rules);
989
990 return rr;
991 }
992
993 /*
994 Service: built-in filter
995 */
996
997 DEFINE_METHOD(log_filter_ruleset *, log_builtins_filter_imp::filter_ruleset_new,
998 (log_filter_tag * tag, size_t count)) {
999 return log_builtins_filter_ruleset_new(tag, count);
1000 }
1001
1002 DEFINE_METHOD(int, log_builtins_filter_imp::filter_ruleset_lock,
1003 (log_filter_ruleset * ruleset,
1004 log_builtins_filter_lock locktype)) {
1005 return log_builtins_filter_ruleset_lock(ruleset, locktype);
1006 }
1007
1008 DEFINE_METHOD(void, log_builtins_filter_imp::filter_ruleset_unlock,
1009 (log_filter_ruleset * ruleset)) {
1010 log_builtins_filter_ruleset_unlock(ruleset);
1011 }
1012
1013 DEFINE_METHOD(void, log_builtins_filter_imp::filter_ruleset_drop,
1014 (log_filter_ruleset * ruleset)) {
1015 log_builtins_filter_ruleset_drop(ruleset);
1016 }
1017
1018 DEFINE_METHOD(void, log_builtins_filter_imp::filter_ruleset_free,
1019 (log_filter_ruleset * *ruleset)) {
1020 log_builtins_filter_ruleset_free(ruleset);
1021 }
1022
1023 DEFINE_METHOD(int, log_builtins_filter_imp::filter_ruleset_move,
1024 (log_filter_ruleset * from, log_filter_ruleset *to)) {
1025 uint32 rule_index;
1026
1027 if (from->count > to->alloc) // do we have enough space in target?
1028 return -1; /* purecov: inspected */
1029
1030 log_builtins_filter_ruleset_drop(to); // clear destination
1031
1032 to->tag = from->tag;
1033
1034 for (rule_index = 0; rule_index < from->count; rule_index++) {
1035 to->rule[rule_index] = from->rule[rule_index];
1036 memset(&from->rule[rule_index], 0, sizeof(log_filter_rule));
1037 }
1038
1039 to->count = from->count;
1040 from->count = 0;
1041
1042 return 0;
1043 }
1044
1045 DEFINE_METHOD(void *, log_builtins_filter_imp::filter_rule_init,
1046 (log_filter_ruleset * ruleset)) {
1047 if (log_filter_ruleset_full(ruleset)) return nullptr;
1048 return (void *)log_builtins_filter_rule_init(ruleset);
1049 }
1050
1051 DEFINE_METHOD(int, log_builtins_filter_imp::filter_run,
1052 (log_filter_ruleset * ruleset, log_line *ll)) {
1053 return log_builtins_filter_run(ruleset, ll);
1054 }
1055
1056 DEFINE_METHOD(log_filter_ruleset *,
1057 log_builtins_filter_debug_imp::filter_debug_ruleset_get, (void)) {
1058 return log_filter_builtin_rules;
1059 }
1060