progress
This commit is contained in:
parent
76b8da4685
commit
bf2249fa7b
110
src/main.c
110
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 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 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_FILTERS 100
|
||||||
#define MAX_URL_PATH_LENGTH 512
|
#define MAX_URL_PATH_LENGTH 512
|
||||||
|
|
||||||
@ -89,12 +89,12 @@ int max_entries = 0;
|
|||||||
int total_entries = 0;
|
int total_entries = 0;
|
||||||
struct filter_system filters = {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) {
|
int is_space(char c) {
|
||||||
return (c == ' ' || c == '\t');
|
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) {
|
char* skip_spaces(char* str) {
|
||||||
while (is_space(*str)) {
|
while (is_space(*str)) {
|
||||||
str++;
|
str++;
|
||||||
@ -102,7 +102,7 @@ char* skip_spaces(char* str) {
|
|||||||
return 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) {
|
void copy_until_space(char* destination, char* source, int max_length) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) {
|
while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) {
|
||||||
@ -129,38 +129,49 @@ int month_name_to_number(char* month_name) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int compare_times(struct simple_time time1, struct simple_time time2) {
|
// Vergleich von Zeitstempel-Strukturen - time_checkvalue ist der Prüfwert, time_filtervalue ist der Vergleichswert (Filter) - Funktion wird in time_matches() aufgerufen
|
||||||
if (time1.year != time2.year) return (time1.year < time2.year) ? -1 : 1;
|
int compare_times(struct simple_time time_checkvalue, struct simple_time time_filtervalue) {
|
||||||
if (time1.month != time2.month) return (time1.month < time2.month) ? -1 : 1;
|
/*
|
||||||
if (time1.day != time2.day) return (time1.day < time2.day) ? -1 : 1;
|
Rückgabewert 1 -> Prüfwert ist NACH dem Vergleichswert
|
||||||
if (time1.hour != time2.hour) return (time1.hour < time2.hour) ? -1 : 1;
|
Es wird erst das Jahr geprüft, dann der Monat u.s.w.
|
||||||
if (time1.minute != time2.minute) return (time1.minute < time2.minute) ? -1 : 1;
|
Ist ein Wert gleich, wird der nächstfeinere Wert geprüft.
|
||||||
if (time1.second != time2.second) return (time1.second < time2.second) ? -1 : 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standardfunktion zum Leeren des Input-Buffers
|
||||||
void clear_input_buffer() {
|
void clear_input_buffer() {
|
||||||
int c;
|
int c;
|
||||||
while ((c = getchar()) != '\n' && c != EOF) {
|
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 read_safe_integer() {
|
||||||
int number;
|
int number;
|
||||||
int result = scanf("%d", &number);
|
int result = scanf("%d", &number);
|
||||||
|
|
||||||
if (result != 1) {
|
if (result != 1) {
|
||||||
clear_input_buffer();
|
clear_input_buffer();
|
||||||
return -1;
|
return -1; // Fehler, wenn die Nutzereingabe kein Integer ist
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_input_buffer();
|
clear_input_buffer();
|
||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle)
|
||||||
void cleanup_memory() {
|
void cleanup_memory() {
|
||||||
if (all_entries != NULL) {
|
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);
|
free(all_entries);
|
||||||
all_entries = NULL;
|
all_entries = NULL;
|
||||||
}
|
}
|
||||||
@ -168,47 +179,49 @@ void cleanup_memory() {
|
|||||||
total_entries = 0;
|
total_entries = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sauberes Schließen und bereinigen bei Fehlerstatus, sofern Speicher nicht alloziert werden kann
|
||||||
void cleanup_and_exit() {
|
void cleanup_and_exit() {
|
||||||
printf("Programm wird wegen Speicherfehler beendet...\n");
|
printf("Speicherfehler, Programmende\n");
|
||||||
cleanup_memory();
|
cleanup_memory();
|
||||||
exit(1);
|
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) {
|
if (total_entries >= max_entries) {
|
||||||
int old_max = max_entries;
|
int old_max = max_entries;
|
||||||
max_entries = max_entries * GROWTH_FACTOR;
|
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));
|
struct log_entry *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry));
|
||||||
|
|
||||||
if (new_ptr == NULL) {
|
if (new_ptr == NULL) {
|
||||||
printf("KRITISCHER FEHLER: Speicher konnte nicht auf %d Einträge erweitert werden!\n", max_entries);
|
printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, Programm wird beendet...\n", max_entries);
|
||||||
printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry));
|
printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
|
||||||
cleanup_and_exit();
|
cleanup_and_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
all_entries = new_ptr;
|
all_entries = new_ptr;
|
||||||
printf("Speicher erfolgreich erweitert auf %zu Bytes\n",
|
printf("Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
|
||||||
max_entries * sizeof(struct log_entry));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void allocate_initial_memory() {
|
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));
|
all_entries = malloc(max_entries * sizeof(struct log_entry));
|
||||||
|
|
||||||
if (all_entries == NULL) {
|
if (all_entries == NULL) {
|
||||||
printf("KRITISCHER FEHLER: Konnte %d Einträge nicht allokieren!\n", max_entries);
|
printf("ERROR: Konnte %d Einträge nicht allozieren, Programm wird beendet...\n", max_entries);
|
||||||
printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry));
|
printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
|
||||||
exit(1);
|
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",
|
printf("Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(max_entries * sizeof(struct log_entry)));
|
||||||
max_entries, 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) {
|
int is_directory(char* path) {
|
||||||
struct stat path_stat;
|
struct stat path_stat;
|
||||||
if (stat(path, &path_stat) != 0) {
|
if (stat(path, &path_stat) != 0) {
|
||||||
@ -217,28 +230,34 @@ int is_directory(char* path) {
|
|||||||
return S_ISDIR(path_stat.st_mode);
|
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) {
|
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;
|
if (log_pos == NULL) return 0;
|
||||||
|
|
||||||
char* after_log = log_pos + 4;
|
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 == '.') {
|
if (*after_log == '.') {
|
||||||
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') {
|
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++;
|
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;
|
char* current_pos = line;
|
||||||
|
|
||||||
current_pos = skip_spaces(current_pos);
|
current_pos = skip_spaces(current_pos);
|
||||||
@ -360,7 +379,7 @@ void load_regular_file(char* filename) {
|
|||||||
int loaded_from_this_file = 0;
|
int loaded_from_this_file = 0;
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), file) != NULL) {
|
while (fgets(line, sizeof(line), file) != NULL) {
|
||||||
expand_memory_if_needed();
|
mem_expand_dynamically();
|
||||||
|
|
||||||
if (parse_simple_log_line(line, total_entries)) {
|
if (parse_simple_log_line(line, total_entries)) {
|
||||||
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("Erfolgreich %d Einträge insgesamt geladen.\n", total_entries);
|
||||||
printf("Aktueller Speicherverbrauch: %zu Bytes für %d Einträge\n",
|
printf("Aktueller Speicherverbrauch: %lu Bytes für %d Einträge\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries);
|
||||||
max_entries * sizeof(struct log_entry), max_entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int status_code_matches(int status_code) {
|
int status_code_matches(int status_code) {
|
||||||
@ -487,35 +505,39 @@ int ip_address_matches(char* ip_address) {
|
|||||||
return 1;
|
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) {
|
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;
|
if (filters.time_count == 0) return 1;
|
||||||
|
|
||||||
int has_include_filters = 0;
|
int has_include_filters = 0;
|
||||||
int include_match = 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++) {
|
for (int i = 0; i < filters.time_count; i++) {
|
||||||
int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 &&
|
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) {
|
if (filters.time_filters[i].mode == FILTER_INCLUDE) {
|
||||||
has_include_filters = 1;
|
has_include_filters = 1; // Flag für inlusive Filter
|
||||||
if (in_range) {
|
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 {
|
} else {
|
||||||
if (in_range) {
|
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) {
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag
|
||||||
int passes_filter(int entry_index) {
|
int passes_filter(int entry_index) {
|
||||||
int status_match = status_code_matches(all_entries[entry_index].status_code);
|
int status_match = status_code_matches(all_entries[entry_index].status_code);
|
||||||
int ip_match = ip_address_matches(all_entries[entry_index].ip_address);
|
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_filtered_entries() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < total_entries; i++) {
|
for (int i = 0; i < total_entries; i++) {
|
||||||
@ -546,8 +569,7 @@ void show_status() {
|
|||||||
|
|
||||||
if (total_entries > 0) {
|
if (total_entries > 0) {
|
||||||
printf("✅ Log-Daten: %d Einträge geladen\n", total_entries);
|
printf("✅ Log-Daten: %d Einträge geladen\n", total_entries);
|
||||||
printf(" Speicherverbrauch: %zu Bytes (%d Einträge Kapazität)\n",
|
printf(" Speicherverbrauch: %lu Bytes (%d Einträge Kapazität)\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries);
|
||||||
max_entries * sizeof(struct log_entry), max_entries);
|
|
||||||
} else {
|
} else {
|
||||||
printf("❌ Keine Log-Daten geladen\n");
|
printf("❌ Keine Log-Daten geladen\n");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user