// SPDX-License-Identifier: GPL-3.0-or-later #include "log2journal.h" // ---------------------------------------------------------------------------- void log_job_init(LOG_JOB *jb) { memset(jb, 0, sizeof(*jb)); simple_hashtable_init_KEY(&jb->hashtable, 32); hashed_key_set(&jb->line.key, "LINE"); } static void simple_hashtable_cleanup_allocated_keys(SIMPLE_HASHTABLE_KEY *ht) { SIMPLE_HASHTABLE_FOREACH_READ_ONLY(ht, sl, _KEY) { HASHED_KEY *k = SIMPLE_HASHTABLE_FOREACH_READ_ONLY_VALUE(sl); if(k && k->flags & HK_HASHTABLE_ALLOCATED) { // the order of these statements is important! simple_hashtable_del_slot_KEY(ht, sl); // remove any references to n hashed_key_cleanup(k); // cleanup the internals of n freez(k); // free n } } } void log_job_cleanup(LOG_JOB *jb) { hashed_key_cleanup(&jb->line.key); if(jb->prefix) { freez((void *) jb->prefix); jb->prefix = NULL; } if(jb->pattern) { freez((void *) jb->pattern); jb->pattern = NULL; } for(size_t i = 0; i < jb->injections.used ;i++) injection_cleanup(&jb->injections.keys[i]); for(size_t i = 0; i < jb->unmatched.injections.used ;i++) injection_cleanup(&jb->unmatched.injections.keys[i]); for(size_t i = 0; i < jb->renames.used ;i++) rename_cleanup(&jb->renames.array[i]); for(size_t i = 0; i < jb->rewrites.used; i++) rewrite_cleanup(&jb->rewrites.array[i]); txt_cleanup(&jb->rewrites.tmp); txt_cleanup(&jb->filename.current); simple_hashtable_cleanup_allocated_keys(&jb->hashtable); simple_hashtable_destroy_KEY(&jb->hashtable); // remove references to everything else, to reveal them in valgrind memset(jb, 0, sizeof(*jb)); } // ---------------------------------------------------------------------------- bool log_job_filename_key_set(LOG_JOB *jb, const char *key, size_t key_len) { if(!key || !*key) { log2stderr("filename key cannot be empty."); return false; } hashed_key_len_set(&jb->filename.key, key, key_len); return true; } bool log_job_key_prefix_set(LOG_JOB *jb, const char *prefix, size_t prefix_len) { if(!prefix || !*prefix) { log2stderr("filename key cannot be empty."); return false; } if(jb->prefix) freez((char*)jb->prefix); jb->prefix = strndupz(prefix, prefix_len); return true; } bool log_job_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) { if(!pattern || !*pattern) { log2stderr("filename key cannot be empty."); return false; } if(jb->pattern) freez((char*)jb->pattern); jb->pattern = strndupz(pattern, pattern_len); return true; } bool log_job_include_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) { if(jb->filter.include.re) { log2stderr("FILTER INCLUDE: there is already an include filter set"); return false; } if(!search_pattern_set(&jb->filter.include, pattern, pattern_len)) { log2stderr("FILTER INCLUDE: failed: %s", jb->filter.include.error.txt); return false; } return true; } bool log_job_exclude_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) { if(jb->filter.exclude.re) { log2stderr("FILTER INCLUDE: there is already an exclude filter set"); return false; } if(!search_pattern_set(&jb->filter.exclude, pattern, pattern_len)) { log2stderr("FILTER EXCLUDE: failed: %s", jb->filter.exclude.error.txt); return false; } return true; } // ---------------------------------------------------------------------------- static bool parse_rename(LOG_JOB *jb, const char *param) { // Search for '=' in param const char *equal_sign = strchr(param, '='); if (!equal_sign || equal_sign == param) { log2stderr("Error: Invalid rename format, '=' not found in %s", param); return false; } const char *new_key = param; size_t new_key_len = equal_sign - new_key; const char *old_key = equal_sign + 1; size_t old_key_len = strlen(old_key); return log_job_rename_add(jb, new_key, new_key_len, old_key, old_key_len); } static bool is_symbol(char c) { return !isalpha(c) && !isdigit(c) && !iscntrl(c); } struct { const char *keyword; int action; RW_FLAGS flag; } rewrite_flags[] = { {"match", 1, RW_MATCH_PCRE2}, {"match", 0, RW_MATCH_NON_EMPTY}, {"regex", 1, RW_MATCH_PCRE2}, {"regex", 0, RW_MATCH_NON_EMPTY}, {"pcre2", 1, RW_MATCH_PCRE2}, {"pcre2", 0, RW_MATCH_NON_EMPTY}, {"non_empty", 1, RW_MATCH_NON_EMPTY}, {"non_empty", 0, RW_MATCH_PCRE2}, {"non-empty", 1, RW_MATCH_NON_EMPTY}, {"non-empty", 0, RW_MATCH_PCRE2}, {"not_empty", 1, RW_MATCH_NON_EMPTY}, {"not_empty", 0, RW_MATCH_PCRE2}, {"not-empty", 1, RW_MATCH_NON_EMPTY}, {"not-empty", 0, RW_MATCH_PCRE2}, {"stop", 0, RW_DONT_STOP}, {"no-stop", 1, RW_DONT_STOP}, {"no_stop", 1, RW_DONT_STOP}, {"dont-stop", 1, RW_DONT_STOP}, {"dont_stop", 1, RW_DONT_STOP}, {"continue", 1, RW_DONT_STOP}, {"inject", 1, RW_INJECT}, {"existing", 0, RW_INJECT}, }; RW_FLAGS parse_rewrite_flags(const char *options) { RW_FLAGS flags = RW_MATCH_PCRE2; // Default option // Tokenize the input options using "," char *token; char *optionsCopy = strdup(options); // Make a copy to avoid modifying the original token = strtok(optionsCopy, ","); while (token != NULL) { // Find the keyword-action mapping bool found = false; for (size_t i = 0; i < sizeof(rewrite_flags) / sizeof(rewrite_flags[0]); i++) { if (strcmp(token, rewrite_flags[i].keyword) == 0) { if (rewrite_flags[i].action == 1) { flags |= rewrite_flags[i].flag; // Set the flag } else { flags &= ~rewrite_flags[i].flag; // Unset the flag } found = true; } } if(!found) log2stderr("Warning: rewrite options '%s' is not understood.", token); // Get the next token token = strtok(NULL, ","); } free(optionsCopy); // Free the copied string return flags; } static bool parse_rewrite(LOG_JOB *jb, const char *param) { // Search for '=' in param const char *equal_sign = strchr(param, '='); if (!equal_sign || equal_sign == param) { log2stderr("Error: Invalid rewrite format, '=' not found in %s", param); return false; } // Get the next character as the separator char separator = *(equal_sign + 1); if (!separator || !is_symbol(separator)) { log2stderr("Error: rewrite separator not found after '=', or is not one of /\\|-# in: %s", param); return false; } // Find the next occurrence of the separator const char *second_separator = strchr(equal_sign + 2, separator); if (!second_separator) { log2stderr("Error: rewrite second separator not found in: %s", param); return false; } // Check if the search pattern is empty if (equal_sign + 1 == second_separator) { log2stderr("Error: rewrite search pattern is empty in: %s", param); return false; } // Check if the replacement pattern is empty if (*(second_separator + 1) == '\0') { log2stderr("Error: rewrite replacement pattern is empty in: %s", param); return false; } RW_FLAGS flags = RW_MATCH_PCRE2; const char *third_separator = strchr(second_separator + 1, separator); if(third_separator) flags = parse_rewrite_flags(third_separator + 1); // Extract key, search pattern, and replacement pattern char *key = strndupz(param, equal_sign - param); char *search_pattern = strndupz(equal_sign + 2, second_separator - (equal_sign + 2)); char *replace_pattern = third_separator ? strndup(second_separator + 1, third_separator - (second_separator + 1)) : strdupz(second_separator + 1); if(!*search_pattern) flags &= ~RW_MATCH_PCRE2; bool ret = log_job_rewrite_add(jb, key, flags, search_pattern, replace_pattern); freez(key); freez(search_pattern); freez(replace_pattern); return ret; } static bool parse_inject(LOG_JOB *jb, const char *value, bool unmatched) { const char *equal = strchr(value, '='); if (!equal) { log2stderr("Error: injection '%s' does not have an equal sign.", value); return false; } const char *key = value; const char *val = equal + 1; log_job_injection_add(jb, key, equal - key, val, strlen(val), unmatched); return true; } bool log_job_command_line_parse_parameters(LOG_JOB *jb, int argc, char **argv) { for (int i = 1; i < argc; i++) { char *arg = argv[i]; if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { log_job_command_line_help(argv[0]); exit(0); } #if defined(NETDATA_DEV_MODE) || defined(NETDATA_INTERNAL_CHECKS) else if(strcmp(arg, "--test") == 0) { // logfmt_test(); json_test(); exit(1); } #endif else if (strcmp(arg, "--show-config") == 0) { jb->show_config = true; } else { char buffer[1024]; char *param = NULL; char *value = NULL; char *equal_sign = strchr(arg, '='); if (equal_sign) { copy_to_buffer(buffer, sizeof(buffer), arg, equal_sign - arg); param = buffer; value = equal_sign + 1; } else { param = arg; if (i + 1 < argc) { value = argv[++i]; } else { if (!jb->pattern) { log_job_pattern_set(jb, arg, strlen(arg)); continue; } else { log2stderr("Error: Multiple patterns detected. Specify only one pattern. The first is '%s', the second is '%s'", jb->pattern, arg); return false; } } } if (strcmp(param, "--filename-key") == 0) { if(!log_job_filename_key_set(jb, value, value ? strlen(value) : 0)) return false; } else if (strcmp(param, "--prefix") == 0) { if(!log_job_key_prefix_set(jb, value, value ? strlen(value) : 0)) return false; } #ifdef HAVE_LIBYAML else if (strcmp(param, "-f") == 0 || strcmp(param, "--file") == 0) { if (!yaml_parse_file(value, jb)) return false; } else if (strcmp(param, "-c") == 0 || strcmp(param, "--config") == 0) { if (!yaml_parse_config(value, jb)) return false; } #endif else if (strcmp(param, "--unmatched-key") == 0) hashed_key_set(&jb->unmatched.key, value); else if (strcmp(param, "--inject") == 0) { if (!parse_inject(jb, value, false)) return false; } else if (strcmp(param, "--inject-unmatched") == 0) { if (!parse_inject(jb, value, true)) return false; } else if (strcmp(param, "--rewrite") == 0) { if (!parse_rewrite(jb, value)) return false; } else if (strcmp(param, "--rename") == 0) { if (!parse_rename(jb, value)) return false; } else if (strcmp(param, "--include") == 0) { if (!log_job_include_pattern_set(jb, value, strlen(value))) return false; } else if (strcmp(param, "--exclude") == 0) { if (!log_job_exclude_pattern_set(jb, value, strlen(value))) return false; } else { i--; if (!jb->pattern) { log_job_pattern_set(jb, arg, strlen(arg)); continue; } else { log2stderr("Error: Multiple patterns detected. Specify only one pattern. The first is '%s', the second is '%s'", jb->pattern, arg); return false; } } } } // Check if a pattern is set and exactly one pattern is specified if (!jb->pattern) { log2stderr("Warning: pattern not specified. Try the default config with: -c default"); log_job_command_line_help(argv[0]); return false; } return true; }