diff --git a/src/main.c b/src/main.c index d78a217..e8247a0 100644 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #define MAX_LINE_LENGTH_BUF 2048 #define INITIAL_ENTRIES 1000 // globale Variable zur initialen Speicherallokation in allocate_initial_memory(). Wird falls nötig um GROWTH_FACTOR erweitert -#define GROWTH_FACTOR 2 // wird in expand_memory_if_needed() genutzt, um den Speicher zu vergrößern +#define GROWTH_FACTOR 2 // wird in mem_expand_dynamically() genutzt, um den Speicher zu vergrößern #define MAX_FILTERS 100 #define MAX_URL_PATH_LENGTH 512 @@ -89,12 +89,12 @@ int max_entries = 0; int total_entries = 0; struct filter_system filters = {0}; -// Helper für die Erkennung von Leerzeichen +// Hilfsfunktion für die Erkennung von Leerzeichen int is_space(char c) { return (c == ' ' || c == '\t'); } -// Helper zum Überspringen von Leerzeichen, gibt den Pointer für das nächste nicht-Leerzeichen zurück. Nötig für strtok-Parser. +// Hilfsfunktion zum Überspringen von Leerzeichen, gibt den Pointer für das nächste nicht-Leerzeichen zurück. Nötig für Parser. char* skip_spaces(char* str) { while (is_space(*str)) { str++; @@ -102,7 +102,7 @@ char* skip_spaces(char* str) { return str; } -// TODO +// Kopiert einen Eingabestring von einem Quellbereich zu einem Zielbereich, bis ein Leerzeichen (oder Nullterminator) erreicht wird void copy_until_space(char* destination, char* source, int max_length) { int i = 0; while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) { @@ -129,38 +129,49 @@ int month_name_to_number(char* month_name) { return 1; } -int compare_times(struct simple_time time1, struct simple_time time2) { - if (time1.year != time2.year) return (time1.year < time2.year) ? -1 : 1; - if (time1.month != time2.month) return (time1.month < time2.month) ? -1 : 1; - if (time1.day != time2.day) return (time1.day < time2.day) ? -1 : 1; - if (time1.hour != time2.hour) return (time1.hour < time2.hour) ? -1 : 1; - if (time1.minute != time2.minute) return (time1.minute < time2.minute) ? -1 : 1; - if (time1.second != time2.second) return (time1.second < time2.second) ? -1 : 1; +// Vergleich von Zeitstempel-Strukturen - time_checkvalue ist der Prüfwert, time_filtervalue ist der Vergleichswert (Filter) - Funktion wird in time_matches() aufgerufen +int compare_times(struct simple_time time_checkvalue, struct simple_time time_filtervalue) { + /* + Rückgabewert 1 -> Prüfwert ist NACH dem Vergleichswert + Es wird erst das Jahr geprüft, dann der Monat u.s.w. + Ist ein Wert gleich, wird der nächstfeinere Wert geprüft. + Sobald die Werte sich unterscheiden, findet die Prüfung statt und ein Rückgabewert wird ausgegeben. + Gibt 0 zurück, wenn der Wert dem Filter exakt gleicht + */ + if (time_checkvalue.year != time_filtervalue.year) return (time_checkvalue.year < time_filtervalue.year) ? -1 : 1; + if (time_checkvalue.month != time_filtervalue.month) return (time_checkvalue.month < time_filtervalue.month) ? -1 : 1; + if (time_checkvalue.day != time_filtervalue.day) return (time_checkvalue.day < time_filtervalue.day) ? -1 : 1; + if (time_checkvalue.hour != time_filtervalue.hour) return (time_checkvalue.hour < time_filtervalue.hour) ? -1 : 1; + if (time_checkvalue.minute != time_filtervalue.minute) return (time_checkvalue.minute < time_filtervalue.minute) ? -1 : 1; + if (time_checkvalue.second != time_filtervalue.second) return (time_checkvalue.second < time_filtervalue.second) ? -1 : 1; return 0; } +// Standardfunktion zum Leeren des Input-Buffers void clear_input_buffer() { int c; while ((c = getchar()) != '\n' && c != EOF) { } } +// Hilfsfunktion zum Prüfen, ob die Zahl eine Dezimalzahl ist. Wird im Menu verwendet, um die Nutzereingabe zu prüfen int read_safe_integer() { int number; int result = scanf("%d", &number); if (result != 1) { clear_input_buffer(); - return -1; + return -1; // Fehler, wenn die Nutzereingabe kein Integer ist } clear_input_buffer(); return number; } +// Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle) void cleanup_memory() { if (all_entries != NULL) { - printf("Gebe %zu Bytes Speicher frei...\n", max_entries * sizeof(struct log_entry)); + printf("%lu Bytes Speicher werden freigegeben\n", (unsigned long)(max_entries * sizeof(struct log_entry))); free(all_entries); all_entries = NULL; } @@ -168,47 +179,49 @@ void cleanup_memory() { total_entries = 0; } +// sauberes Schließen und bereinigen bei Fehlerstatus, sofern Speicher nicht alloziert werden kann void cleanup_and_exit() { - printf("Programm wird wegen Speicherfehler beendet...\n"); + printf("Speicherfehler, Programmende\n"); cleanup_memory(); exit(1); } -void expand_memory_if_needed() { +// Erweiterung des Speichers für dynamische Speicherallokation +void mem_expand_dynamically() { + // total_entries werden beim parsen am Anfang gezählt (load_regular_file()), max_entries werden initial festgelegt if (total_entries >= max_entries) { int old_max = max_entries; max_entries = max_entries * GROWTH_FACTOR; - printf("Speicher wird erweitert: %d -> %d Einträge\n", old_max, max_entries); + printf("Dynamische Speichererweiterung von %d auf %d Einträge um Faktor %d\n", old_max, max_entries, GROWTH_FACTOR); struct log_entry *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry)); if (new_ptr == NULL) { - printf("KRITISCHER FEHLER: Speicher konnte nicht auf %d Einträge erweitert werden!\n", max_entries); - printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry)); + printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, Programm wird beendet...\n", max_entries); + printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); cleanup_and_exit(); } all_entries = new_ptr; - printf("Speicher erfolgreich erweitert auf %zu Bytes\n", - max_entries * sizeof(struct log_entry)); + printf("Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); } } void allocate_initial_memory() { - max_entries = INITIAL_ENTRIES; + max_entries = INITIAL_ENTRIES; // Startwert 1000, globale Variable all_entries = malloc(max_entries * sizeof(struct log_entry)); if (all_entries == NULL) { - printf("KRITISCHER FEHLER: Konnte %d Einträge nicht allokieren!\n", max_entries); - printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry)); - exit(1); + printf("ERROR: Konnte %d Einträge nicht allozieren, Programm wird beendet...\n", max_entries); + printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); + exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch } - printf("Speicher allokiert für %d Log-Einträge (%zu Bytes)\n", - max_entries, max_entries * sizeof(struct log_entry)); + printf("Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(max_entries * sizeof(struct log_entry))); } +// Hilfsfunktion zum Prüfen, ob es sich beim Pfad um ein Directory handelt - für rekursives Parsen int is_directory(char* path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { @@ -217,28 +230,34 @@ int is_directory(char* path) { return S_ISDIR(path_stat.st_mode); } +// Hilfsfunktion zum prüfen, ob es sich um eine plausible nginx-Logdatei handelt (Metrik: Dateiname - BESSER: Regex oder Magic Bytes?) int is_log_file(char* filename) { - char* log_pos = strstr(filename, ".log"); + char* log_pos = strstr(filename, ".log"); // Sucht und findet den Pointer auf die Startposition des Suchstrings if (log_pos == NULL) return 0; char* after_log = log_pos + 4; - if (*after_log == '\0') return 1; + if (*after_log == '\0') return 1; // true, wenn die Datei mit .log endet if (*after_log == '.') { after_log++; - if (*after_log == '\0') return 0; + if (*after_log == '\0') return 0; // false, wenn die Datei mit einem Punkt nach .log endet while (*after_log != '\0') { - if (*after_log < '0' || *after_log > '9') return 0; + if (*after_log < '0' || *after_log > '9') return 0; // wenn was anderes als Zahlen nach dem . kommen, gib False zurück after_log++; } - return 1; + return 1; // true, wenn nach .log. noch Zahlen vorhanden sind, wie typisch bei NGINX-Logrotation } - return 0; + return 0; // false, wenn nichts zutrifft } -int parse_simple_log_line(char* line, int entry_index) { +/* +Parser. Regex Parser und strtok haben sich als schwieriger herausgestellt, da nginx Leerzeichen-getrennte Werte in den Logs hat, aber auch Leerzeichen innerhalb der Werte vorkommen. +Daher ist das Parsing am einfachsten, wenn ein Pointer-basierter Algorithmus die Zeile Stück für Stück einliest und die Erwartungswerte in die struct schreibt. +Fehleranfällig, wenn das Logformat nicht dem Standard entspricht - das gilt aber auch für andere Parser. +*/ +int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer auf die Zeile und einen Index entgegen - dieser ist anfangs 0 (globale Variable) und wird pro Eintrag inkrementiert char* current_pos = line; current_pos = skip_spaces(current_pos); @@ -360,7 +379,7 @@ void load_regular_file(char* filename) { int loaded_from_this_file = 0; while (fgets(line, sizeof(line), file) != NULL) { - expand_memory_if_needed(); + mem_expand_dynamically(); if (parse_simple_log_line(line, total_entries)) { total_entries++; @@ -431,8 +450,7 @@ void load_log_file(char* path) { } printf("Erfolgreich %d Einträge insgesamt geladen.\n", total_entries); - printf("Aktueller Speicherverbrauch: %zu Bytes für %d Einträge\n", - max_entries * sizeof(struct log_entry), max_entries); + printf("Aktueller Speicherverbrauch: %lu Bytes für %d Einträge\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries); } int status_code_matches(int status_code) { @@ -487,35 +505,39 @@ int ip_address_matches(char* ip_address) { return 1; } +// Vergleicht einen übergebenen Prüfwert für einen Zeitstempel mit dem aktuell gesetzten Filter. Wenn der Prüfwert im Filterbereich ist,wird 1 zurückgegeben. int time_matches(struct simple_time entry_time) { + // gibt 1 zurück, wenn kein Filter gesetzt wird -> der komplette Log-Datensatz wird ausgegeben if (filters.time_count == 0) return 1; int has_include_filters = 0; int include_match = 0; + // es können mehrere Filter gleichzeitig gesetzt sein, diese werden alle nacheinander in einer Schleife geprüft for (int i = 0; i < filters.time_count; i++) { int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 && - compare_times(entry_time, filters.time_filters[i].end_time) <= 0); - + compare_times(entry_time, filters.time_filters[i].end_time) <= 0); // gibt 0=false zurück, wenn der Prüfwert nicht im Filterbereich ist if (filters.time_filters[i].mode == FILTER_INCLUDE) { - has_include_filters = 1; + has_include_filters = 1; // Flag für inlusive Filter if (in_range) { - include_match = 1; + include_match = 1; // gibt 1=true aus, wenn der Prüfwert in den geprüften inklusiven Filter passt } + // ausschließen-Filter } else { if (in_range) { - return 0; + return 0; // gibt 0=false aus, wenn der geprüfte Filter exklusiv ist, und der Prüfwert in den Filterbereich passt } } } if (has_include_filters) { - return include_match; + return include_match; // gibt 0=false aus, wenn es inklusive Filter gibt (has_include_filters =1), aber kein Zeitstempel in den Filterbereich passt } return 1; } +// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag int passes_filter(int entry_index) { int status_match = status_code_matches(all_entries[entry_index].status_code); int ip_match = ip_address_matches(all_entries[entry_index].ip_address); @@ -531,6 +553,7 @@ int passes_filter(int entry_index) { } } +// Einfacher Zähler für alle Einträge, die die Filterfunktionen bestehen int count_filtered_entries() { int count = 0; for (int i = 0; i < total_entries; i++) { @@ -546,8 +569,7 @@ void show_status() { if (total_entries > 0) { printf("✅ Log-Daten: %d Einträge geladen\n", total_entries); - printf(" Speicherverbrauch: %zu Bytes (%d Einträge Kapazität)\n", - max_entries * sizeof(struct log_entry), max_entries); + printf(" Speicherverbrauch: %lu Bytes (%d Einträge Kapazität)\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries); } else { printf("❌ Keine Log-Daten geladen\n"); }