diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-25 17:33:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-25 17:34:10 +0000 |
commit | 83ba6762cc43d9db581b979bb5e3445669e46cc2 (patch) | |
tree | 2e69833b43f791ed253a7a20318b767ebe56cdb8 /src/collectors/windows-events.plugin/windows-events.c | |
parent | Releasing debian version 1.47.5-1. (diff) | |
download | netdata-83ba6762cc43d9db581b979bb5e3445669e46cc2.tar.xz netdata-83ba6762cc43d9db581b979bb5e3445669e46cc2.zip |
Merging upstream version 2.0.3+dfsg (Closes: #923993, #1042533, #1045145).
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/collectors/windows-events.plugin/windows-events.c')
-rw-r--r-- | src/collectors/windows-events.plugin/windows-events.c | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/src/collectors/windows-events.plugin/windows-events.c b/src/collectors/windows-events.plugin/windows-events.c new file mode 100644 index 000000000..09ce558ae --- /dev/null +++ b/src/collectors/windows-events.plugin/windows-events.c @@ -0,0 +1,1402 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" +#include "libnetdata/required_dummies.h" + +#include "windows-events.h" + +netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER; +static bool plugin_should_exit = false; + +#define WEVT_ALWAYS_VISIBLE_KEYS NULL + +#define WEVT_KEYS_EXCLUDED_FROM_FACETS \ + "|" WEVT_FIELD_MESSAGE \ + "|" WEVT_FIELD_XML \ + "" + +#define WEVT_KEYS_INCLUDED_IN_FACETS \ + "|" WEVT_FIELD_COMPUTER \ + "|" WEVT_FIELD_PROVIDER \ + "|" WEVT_FIELD_LEVEL \ + "|" WEVT_FIELD_KEYWORDS \ + "|" WEVT_FIELD_OPCODE \ + "|" WEVT_FIELD_TASK \ + "|" WEVT_FIELD_ACCOUNT \ + "|" WEVT_FIELD_DOMAIN \ + "|" WEVT_FIELD_SID \ + "" + +#define query_has_fts(lqs) ((lqs)->rq.query != NULL) + +static inline WEVT_QUERY_STATUS check_stop(const bool *cancelled, const usec_t *stop_monotonic_ut) { + if(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)) { + nd_log(NDLS_COLLECTORS, NDLP_INFO, "Function has been cancelled"); + return WEVT_CANCELLED; + } + + if(now_monotonic_usec() > __atomic_load_n(stop_monotonic_ut, __ATOMIC_RELAXED)) { + internal_error(true, "Function timed out"); + return WEVT_TIMED_OUT; + } + + return WEVT_OK; +} + +FACET_ROW_SEVERITY wevt_levelid_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) { + FACET_ROW_KEY_VALUE *levelid_rkv = dictionary_get(row->dict, WEVT_FIELD_LEVEL "ID"); + if(!levelid_rkv || levelid_rkv->empty) + return FACET_ROW_SEVERITY_NORMAL; + + int windows_event_level = str2i(buffer_tostring(levelid_rkv->wb)); + + switch (windows_event_level) { + case WEVT_LEVEL_VERBOSE: + return FACET_ROW_SEVERITY_DEBUG; + + default: + case WEVT_LEVEL_INFORMATION: + return FACET_ROW_SEVERITY_NORMAL; + + case WEVT_LEVEL_WARNING: + return FACET_ROW_SEVERITY_WARNING; + + case WEVT_LEVEL_ERROR: + case WEVT_LEVEL_CRITICAL: + return FACET_ROW_SEVERITY_CRITICAL; + } +} + +struct wevt_bin_data { + bool rendered; + WEVT_EVENT ev; + WEVT_LOG *log; + EVT_HANDLE hEvent; + PROVIDER_META_HANDLE *provider; +}; + +static void wevt_cleanup_bin_data(void *data) { + struct wevt_bin_data *d = data; + + if(d->hEvent) + EvtClose(d->hEvent); + + provider_release(d->provider); + freez(d); +} + +static inline void wevt_facets_register_bin_data(WEVT_LOG *log, FACETS *facets, WEVT_EVENT *ev) { + struct wevt_bin_data *d = mallocz(sizeof(struct wevt_bin_data)); + +#ifdef NETDATA_INTERNAL_CHECKS + internal_fatal(strcmp(log->ops.provider.data, provider_get_name(log->provider)) != 0, + "Provider name mismatch in data!"); + + internal_fatal(!UUIDeq(ev->provider, provider_get_uuid(log->provider)), + "Provider UUID mismatch in data!"); +#endif + + d->ev = *ev; + d->log = log; + d->rendered = false; + + // take the bookmark + d->hEvent = log->hEvent; log->hEvent = NULL; + + // dup the provider + d->provider = provider_dup(log->provider); + + facets_row_bin_data_set(facets, wevt_cleanup_bin_data, d); +} + +static void wevt_lazy_loading_event_and_xml(struct wevt_bin_data *d, FACET_ROW *row __maybe_unused) { + if(d->rendered) return; + +#ifdef NETDATA_INTERNAL_CHECKS + const FACET_ROW_KEY_VALUE *provider_rkv = dictionary_get(row->dict, WEVT_FIELD_PROVIDER); + internal_fatal(!provider_rkv || strcmp(buffer_tostring(provider_rkv->wb), provider_get_name(d->provider)) != 0, + "Provider of row does not match the bin data associated with it"); + + uint64_t event_record_id = UINT64_MAX; + const FACET_ROW_KEY_VALUE *event_record_id_rkv = dictionary_get(row->dict, WEVT_FIELD_EVENTRECORDID); + if(event_record_id_rkv) + event_record_id = str2uint64_t(buffer_tostring(event_record_id_rkv->wb), NULL); + internal_fatal(event_record_id != d->ev.id, + "Event Record ID of row does not match the bin data associated with it"); +#endif + + // the message needs the xml + EvtFormatMessage_Xml_utf8(&d->log->ops.unicode, d->provider, d->hEvent, &d->log->ops.xml); + EvtFormatMessage_Event_utf8(&d->log->ops.unicode, d->provider, d->hEvent, &d->log->ops.event); + d->rendered = true; +} + +static void wevt_lazy_load_xml( + FACETS *facets, + BUFFER *json_array, + FACET_ROW_KEY_VALUE *rkv __maybe_unused, + FACET_ROW *row, + void *data __maybe_unused) { + + struct wevt_bin_data *d = facets_row_bin_data_get(facets, row); + if(!d) { + buffer_json_add_array_item_string(json_array, "Failed to get row BIN DATA from facets"); + return; + } + + wevt_lazy_loading_event_and_xml(d, row); + buffer_json_add_array_item_string(json_array, d->log->ops.xml.data); +} + +static void wevt_lazy_load_message( + FACETS *facets, + BUFFER *json_array, + FACET_ROW_KEY_VALUE *rkv __maybe_unused, + FACET_ROW *row, + void *data __maybe_unused) { + + struct wevt_bin_data *d = facets_row_bin_data_get(facets, row); + if(!d) { + buffer_json_add_array_item_string(json_array, "Failed to get row BIN DATA from facets"); + return; + } + + wevt_lazy_loading_event_and_xml(d, row); + + if(d->log->ops.event.used <= 1) { + TXT_UTF8 *xml = &d->log->ops.xml; + + buffer_flush(rkv->wb); + + bool added_message = false; + if(xml->used > 1) { + const char *message_path[] = { + "RenderingInfo", + "Message", + NULL}; + + added_message = buffer_xml_extract_and_print_value( + rkv->wb, + xml->data, xml->used - 1, + NULL, + message_path); + } + + if(!added_message) { + const FACET_ROW_KEY_VALUE *event_id_rkv = dictionary_get(row->dict, WEVT_FIELD_EVENTID); + if (event_id_rkv && buffer_strlen(event_id_rkv->wb)) { + buffer_fast_strcat(rkv->wb, "Event ", 6); + buffer_fast_strcat(rkv->wb, buffer_tostring(event_id_rkv->wb), buffer_strlen(event_id_rkv->wb)); + } else + buffer_strcat(rkv->wb, "Unknown Event "); + + const FACET_ROW_KEY_VALUE *provider_rkv = dictionary_get(row->dict, WEVT_FIELD_PROVIDER); + if (provider_rkv && buffer_strlen(provider_rkv->wb)) { + buffer_fast_strcat(rkv->wb, " of ", 4); + buffer_fast_strcat(rkv->wb, buffer_tostring(provider_rkv->wb), buffer_strlen(provider_rkv->wb)); + buffer_putc(rkv->wb, '.'); + } else + buffer_strcat(rkv->wb, "of unknown Provider."); + } + + if(xml->used > 1) { + const char *event_path[] = { + "EventData", + NULL + }; + bool added_event_data = buffer_extract_and_print_xml( + rkv->wb, + xml->data, xml->used - 1, + "\n\nRelated event data:\n", + event_path); + + const char *user_path[] = { + "UserData", + NULL + }; + bool added_user_data = buffer_extract_and_print_xml( + rkv->wb, + xml->data, xml->used - 1, + "\n\nRelated user data:\n", + user_path); + + if(!added_event_data && !added_user_data) + buffer_strcat(rkv->wb, " Without any related data."); + } + + buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb)); + } + else + buffer_json_add_array_item_string(json_array, d->log->ops.event.data); +} + +static void wevt_register_fields(LOGS_QUERY_STATUS *lqs) { + // the order of the fields here, controls the order of the fields at the table presented + + FACETS *facets = lqs->facets; + LOGS_QUERY_REQUEST *rq = &lqs->rq; + + facets_register_row_severity(facets, wevt_levelid_to_facet_severity, NULL); + + facets_register_key_name( + facets, WEVT_FIELD_COMPUTER, + rq->default_facet | FACET_KEY_OPTION_VISIBLE); + + facets_register_key_name( + facets, WEVT_FIELD_CHANNEL, + rq->default_facet | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_PROVIDER, + rq->default_facet | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_ACCOUNT, + rq->default_facet | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_DOMAIN, + rq->default_facet | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_SID, + rq->default_facet | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_EVENTID, + rq->default_facet | + FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_EVENTS_API, + rq->default_facet | + FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_LEVEL, + rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_EXPANDED_FILTER); + + facets_register_key_name( + facets, WEVT_FIELD_LEVEL "ID", + FACET_KEY_OPTION_NONE); + + facets_register_key_name( + facets, WEVT_FIELD_PROCESSID, + FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_THREADID, + FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_TASK, + rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_VISIBLE); + + facets_register_key_name( + facets, WEVT_FIELD_TASK "ID", + FACET_KEY_OPTION_NONE); + + facets_register_key_name( + facets, WEVT_FIELD_OPCODE, + rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_VISIBLE); + + facets_register_key_name( + facets, WEVT_FIELD_OPCODE "ID", + FACET_KEY_OPTION_NONE); + + facets_register_key_name( + facets, WEVT_FIELD_KEYWORDS, + rq->default_facet | FACET_KEY_OPTION_FTS); + + facets_register_key_name( + facets, WEVT_FIELD_KEYWORDS "ID", + FACET_KEY_OPTION_NONE); + + facets_register_dynamic_key_name( + facets, + WEVT_FIELD_MESSAGE, + FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_VISIBLE, + wevt_lazy_load_message, + NULL); + + facets_register_dynamic_key_name( + facets, + WEVT_FIELD_XML, + FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_PRETTY_XML, + wevt_lazy_load_xml, + NULL); + + if(query_has_fts(lqs)) { + facets_register_key_name( + facets, WEVT_FIELD_EVENT_MESSAGE_HIDDEN, + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET); + + facets_register_key_name( + facets, WEVT_FIELD_EVENT_XML_HIDDEN, + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET); + + facets_register_key_name( + facets, WEVT_FIELD_EVENT_DATA_HIDDEN, + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET); + } + +#ifdef NETDATA_INTERNAL_CHECKS + facets_register_key_name( + facets, "z_level_source", + rq->default_facet); + + facets_register_key_name( + facets, "z_keywords_source", + rq->default_facet); + + facets_register_key_name( + facets, "z_opcode_source", + rq->default_facet); + + facets_register_key_name( + facets, "z_task_source", + rq->default_facet); +#endif +} + +#ifdef NETDATA_INTERNAL_CHECKS +static const char *source_to_str(TXT_UTF8 *txt) { + switch(txt->src) { + default: + case TXT_SOURCE_UNKNOWN: + return "unknown"; + + case TXT_SOURCE_EVENT_LOG: + return "event-log"; + + case TXT_SOURCE_PROVIDER: + return "provider"; + + case TXT_SOURCE_FIELD_CACHE: + return "fields-cache"; + + case TXT_SOURCE_HARDCODED: + return "hardcoded"; + } +} +#endif + +static const char *events_api_to_str(WEVT_PROVIDER_PLATFORM platform) { + switch(platform) { + case WEVT_PLATFORM_WEL: + return "Windows Event Log"; + + case WEVT_PLATFORM_ETW: + return "Event Tracing for Windows"; + + case WEVT_PLATFORM_TL: + return "TraceLogging"; + + default: + return "Unknown"; + } +} + +static inline size_t wevt_process_event(WEVT_LOG *log, FACETS *facets, LOGS_QUERY_SOURCE *src, usec_t *msg_ut __maybe_unused, WEVT_EVENT *ev) { + static __thread char uuid_str[UUID_STR_LEN]; + + size_t len, bytes = log->ops.raw.system.used + log->ops.raw.user.used; + + if(!UUIDiszero(ev->provider)) { + uuid_unparse_lower(ev->provider.uuid, uuid_str); + facets_add_key_value_length( + facets, WEVT_FIELD_PROVIDER_GUID, sizeof(WEVT_FIELD_PROVIDER_GUID) - 1, + uuid_str, sizeof(uuid_str) - 1); + } + + if(!UUIDiszero(ev->activity_id)) { + uuid_unparse_lower(ev->activity_id.uuid, uuid_str); + facets_add_key_value_length( + facets, WEVT_FIELD_ACTIVITY_ID, sizeof(WEVT_FIELD_ACTIVITY_ID) - 1, + uuid_str, sizeof(uuid_str) - 1); + } + + if(!UUIDiszero(ev->related_activity_id)) { + uuid_unparse_lower(ev->related_activity_id.uuid, uuid_str); + facets_add_key_value_length( + facets, WEVT_FIELD_RELATED_ACTIVITY_ID, sizeof(WEVT_FIELD_RELATED_ACTIVITY_ID) - 1, + uuid_str, sizeof(uuid_str) - 1); + } + + if(ev->qualifiers) { + static __thread char qualifiers[UINT64_HEX_MAX_LENGTH]; + len = print_uint64_hex(qualifiers, ev->qualifiers); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_QUALIFIERS, sizeof(WEVT_FIELD_QUALIFIERS) - 1, + qualifiers, len); + } + + { + static __thread char event_record_id_str[UINT64_MAX_LENGTH]; + len = print_uint64(event_record_id_str, ev->id); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_EVENTRECORDID, sizeof(WEVT_FIELD_EVENTRECORDID) - 1, + event_record_id_str, len); + } + + if(ev->version) { + static __thread char version[UINT64_MAX_LENGTH]; + len = print_uint64(version, ev->version); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_VERSION, sizeof(WEVT_FIELD_VERSION) - 1, + version, len); + } + + if(log->ops.provider.used > 1) { + bytes += log->ops.provider.used * 2; // unicode is double + facets_add_key_value_length( + facets, WEVT_FIELD_PROVIDER, sizeof(WEVT_FIELD_PROVIDER) - 1, + log->ops.provider.data, log->ops.provider.used - 1); + } + + if(log->ops.channel.used > 1) { + bytes += log->ops.channel.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_CHANNEL, sizeof(WEVT_FIELD_CHANNEL) - 1, + log->ops.channel.data, log->ops.channel.used - 1); + } + else { + bytes += src->fullname_len * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_CHANNEL, sizeof(WEVT_FIELD_CHANNEL) - 1, + src->fullname, src->fullname_len); + } + + if(log->ops.level.used > 1) { + bytes += log->ops.level.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_LEVEL, sizeof(WEVT_FIELD_LEVEL) - 1, + log->ops.level.data, log->ops.level.used - 1); + } + + if(log->ops.computer.used > 1) { + bytes += log->ops.computer.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_COMPUTER, sizeof(WEVT_FIELD_COMPUTER) - 1, + log->ops.computer.data, log->ops.computer.used - 1); + } + + if(log->ops.opcode.used > 1) { + bytes += log->ops.opcode.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_OPCODE, sizeof(WEVT_FIELD_OPCODE) - 1, + log->ops.opcode.data, log->ops.opcode.used - 1); + } + + if(log->ops.keywords.used > 1) { + bytes += log->ops.keywords.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_KEYWORDS, sizeof(WEVT_FIELD_KEYWORDS) - 1, + log->ops.keywords.data, log->ops.keywords.used - 1); + } + + if(log->ops.task.used > 1) { + bytes += log->ops.task.used * 2; + facets_add_key_value_length( + facets, WEVT_FIELD_TASK, sizeof(WEVT_FIELD_TASK) - 1, + log->ops.task.data, log->ops.task.used - 1); + } + + if(log->ops.account.used > 1) { + bytes += log->ops.account.used * 2; + facets_add_key_value_length( + facets, + WEVT_FIELD_ACCOUNT, sizeof(WEVT_FIELD_ACCOUNT) - 1, + log->ops.account.data, log->ops.account.used - 1); + } + + if(log->ops.domain.used > 1) { + bytes += log->ops.domain.used * 2; + facets_add_key_value_length( + facets, + WEVT_FIELD_DOMAIN, sizeof(WEVT_FIELD_DOMAIN) - 1, + log->ops.domain.data, log->ops.domain.used - 1); + } + + if(log->ops.sid.used > 1) { + bytes += log->ops.sid.used * 2; + facets_add_key_value_length( + facets, + WEVT_FIELD_SID, sizeof(WEVT_FIELD_SID) - 1, + log->ops.sid.data, log->ops.sid.used - 1); + } + + { + static __thread char event_id_str[UINT64_MAX_LENGTH]; + len = print_uint64(event_id_str, ev->event_id); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_EVENTID, sizeof(WEVT_FIELD_EVENTID) - 1, + event_id_str, len); + } + + { + const char *s = events_api_to_str(ev->platform); + facets_add_key_value_length( + facets, WEVT_FIELD_EVENTS_API, sizeof(WEVT_FIELD_EVENTS_API) - 1, s, strlen(s)); + } + + if(ev->process_id) { + static __thread char process_id_str[UINT64_MAX_LENGTH]; + len = print_uint64(process_id_str, ev->process_id); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_PROCESSID, sizeof(WEVT_FIELD_PROCESSID) - 1, + process_id_str, len); + } + + if(ev->thread_id) { + static __thread char thread_id_str[UINT64_MAX_LENGTH]; + len = print_uint64(thread_id_str, ev->thread_id); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_THREADID, sizeof(WEVT_FIELD_THREADID) - 1, + thread_id_str, len); + } + + { + static __thread char str[UINT64_MAX_LENGTH]; + len = print_uint64(str, ev->level); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_LEVEL "ID", sizeof(WEVT_FIELD_LEVEL) + 2 - 1, str, len); + } + + { + static __thread char str[UINT64_HEX_MAX_LENGTH]; + len = print_uint64_hex_full(str, ev->keywords); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_KEYWORDS "ID", sizeof(WEVT_FIELD_KEYWORDS) + 2 - 1, str, len); + } + + { + static __thread char str[UINT64_MAX_LENGTH]; + len = print_uint64(str, ev->opcode); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_OPCODE "ID", sizeof(WEVT_FIELD_OPCODE) + 2 - 1, str, len); + } + + { + static __thread char str[UINT64_MAX_LENGTH]; + len = print_uint64(str, ev->task); + bytes += len; + facets_add_key_value_length( + facets, WEVT_FIELD_TASK "ID", sizeof(WEVT_FIELD_TASK) + 2 - 1, str, len); + } + + if(log->type & WEVT_QUERY_EVENT_DATA) { + // the query has full text-search + if(log->ops.event.used > 1) { + bytes += log->ops.event.used; + facets_add_key_value_length( + facets, WEVT_FIELD_EVENT_MESSAGE_HIDDEN, sizeof(WEVT_FIELD_EVENT_MESSAGE_HIDDEN) - 1, + log->ops.event.data, log->ops.event.used - 1); + } + + if(log->ops.xml.used > 1) { + bytes += log->ops.xml.used; + facets_add_key_value_length( + facets, WEVT_FIELD_EVENT_XML_HIDDEN, sizeof(WEVT_FIELD_EVENT_XML_HIDDEN) - 1, + log->ops.xml.data, log->ops.xml.used - 1); + } + + if(log->ops.event_data->len) { + bytes += log->ops.event_data->len; + facets_add_key_value_length( + facets, WEVT_FIELD_EVENT_DATA_HIDDEN, sizeof(WEVT_FIELD_EVENT_DATA_HIDDEN) - 1, + buffer_tostring(log->ops.event_data), buffer_strlen(log->ops.event_data)); + } + } + + wevt_facets_register_bin_data(log, facets, ev); + +#ifdef NETDATA_INTERNAL_CHECKS + facets_add_key_value(facets, "z_level_source", source_to_str(&log->ops.level)); + facets_add_key_value(facets, "z_keywords_source", source_to_str(&log->ops.keywords)); + facets_add_key_value(facets, "z_opcode_source", source_to_str(&log->ops.opcode)); + facets_add_key_value(facets, "z_task_source", source_to_str(&log->ops.task)); +#endif + + return bytes; +} + +static void send_progress_update(LOGS_QUERY_STATUS *lqs, size_t current_row_counter, bool flush_current_file) { + usec_t now_ut = now_monotonic_usec(); + + if(current_row_counter > lqs->c.progress.entries.current_query_total) { + lqs->c.progress.entries.total += current_row_counter - lqs->c.progress.entries.current_query_total; + lqs->c.progress.entries.current_query_total = current_row_counter; + } + + if(flush_current_file) { + lqs->c.progress.entries.total += current_row_counter; + lqs->c.progress.entries.total -= lqs->c.progress.entries.current_query_total; + lqs->c.progress.entries.completed += current_row_counter; + lqs->c.progress.entries.current_query_total = 0; + } + + size_t completed = lqs->c.progress.entries.completed + current_row_counter; + if(completed > lqs->c.progress.entries.total) + lqs->c.progress.entries.total = completed; + + usec_t progress_duration_ut = now_ut - lqs->c.progress.last_ut; + if(progress_duration_ut >= WINDOWS_EVENTS_PROGRESS_EVERY_UT) { + lqs->c.progress.last_ut = now_ut; + + netdata_mutex_lock(&stdout_mutex); + pluginsd_function_progress_to_stdout(lqs->rq.transaction, completed, lqs->c.progress.entries.total); + netdata_mutex_unlock(&stdout_mutex); + } +} + +static WEVT_QUERY_STATUS wevt_query_backward( + WEVT_LOG *log, BUFFER *wb __maybe_unused, FACETS *facets, + LOGS_QUERY_SOURCE *src, + LOGS_QUERY_STATUS *lqs) +{ + usec_t start_ut = lqs->query.start_ut; + usec_t stop_ut = lqs->query.stop_ut; + bool stop_when_full = lqs->query.stop_when_full; + +// lqs->c.query_file.start_ut = start_ut; +// lqs->c.query_file.stop_ut = stop_ut; + + if(!wevt_query(log, channel2unicode(src->fullname), lqs->c.query, EvtQueryReverseDirection)) + return WEVT_FAILED_TO_SEEK; + + size_t errors_no_timestamp = 0; + usec_t latest_msg_ut = 0; // the biggest timestamp we have seen so far + usec_t first_msg_ut = 0; // the first message we got from the db + size_t row_counter = 0, last_row_counter = 0, rows_useful = 0; + size_t bytes = 0, last_bytes = 0; + + usec_t last_usec_from = 0; + usec_t last_usec_to = 0; + + WEVT_QUERY_STATUS status = WEVT_OK; + + facets_rows_begin(facets); + WEVT_EVENT e; + while (status == WEVT_OK && wevt_get_next_event(log, &e)) { + usec_t msg_ut = e.created_ns / NSEC_PER_USEC; + + if(unlikely(!msg_ut)) { + errors_no_timestamp++; + continue; + } + + if (unlikely(msg_ut > start_ut)) + continue; + + if (unlikely(msg_ut < stop_ut)) + break; + + if(unlikely(msg_ut > latest_msg_ut)) + latest_msg_ut = msg_ut; + + if(unlikely(!first_msg_ut)) { + first_msg_ut = msg_ut; + // lqs->c.query_file.first_msg_ut = msg_ut; + } + +// sampling_t sample = is_row_in_sample(log, lqs, src, msg_ut, +// FACETS_ANCHOR_DIRECTION_BACKWARD, +// facets_row_candidate_to_keep(facets, msg_ut)); +// +// if(sample == SAMPLING_FULL) { + bytes += wevt_process_event(log, facets, src, &msg_ut, &e); + + // make sure each line gets a unique timestamp + if(unlikely(msg_ut >= last_usec_from && msg_ut <= last_usec_to)) + msg_ut = --last_usec_from; + else + last_usec_from = last_usec_to = msg_ut; + + if(facets_row_finished(facets, msg_ut)) + rows_useful++; + + row_counter++; + if(unlikely((row_counter % FUNCTION_DATA_ONLY_CHECK_EVERY_ROWS) == 0 && + stop_when_full && + facets_rows(facets) >= lqs->rq.entries)) { + // stop the data only query + usec_t oldest = facets_row_oldest_ut(facets); + if(oldest && msg_ut < (oldest - lqs->anchor.delta_ut)) + break; + } + + if(unlikely(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0)) { + status = check_stop(lqs->cancelled, lqs->stop_monotonic_ut); + + if(status == WEVT_OK) { + lqs->c.rows_read += row_counter - last_row_counter; + last_row_counter = row_counter; + + lqs->c.bytes_read += bytes - last_bytes; + last_bytes = bytes; + + send_progress_update(lqs, row_counter, false); + } + } +// } +// else if(sample == SAMPLING_SKIP_FIELDS) +// facets_row_finished_unsampled(facets, msg_ut); +// else { +// sampling_update_running_query_file_estimates(facets, log, lqs, src, msg_ut, FACETS_ANCHOR_DIRECTION_BACKWARD); +// break; +// } + } + + send_progress_update(lqs, row_counter, true); + lqs->c.rows_read += row_counter - last_row_counter; + lqs->c.bytes_read += bytes - last_bytes; + lqs->c.rows_useful += rows_useful; + + if(errors_no_timestamp) + netdata_log_error("WINDOWS-EVENTS: %zu events did not have timestamps", errors_no_timestamp); + + if(latest_msg_ut > lqs->last_modified) + lqs->last_modified = latest_msg_ut; + + wevt_query_done(log); + + return status; +} + +static WEVT_QUERY_STATUS wevt_query_forward( + WEVT_LOG *log, BUFFER *wb __maybe_unused, FACETS *facets, + LOGS_QUERY_SOURCE *src, + LOGS_QUERY_STATUS *lqs) +{ + usec_t start_ut = lqs->query.start_ut; + usec_t stop_ut = lqs->query.stop_ut; + bool stop_when_full = lqs->query.stop_when_full; + +// lqs->c.query_file.start_ut = start_ut; +// lqs->c.query_file.stop_ut = stop_ut; + + if(!wevt_query(log, channel2unicode(src->fullname), lqs->c.query, EvtQueryForwardDirection)) + return WEVT_FAILED_TO_SEEK; + + size_t errors_no_timestamp = 0; + usec_t latest_msg_ut = 0; // the biggest timestamp we have seen so far + usec_t first_msg_ut = 0; // the first message we got from the db + size_t row_counter = 0, last_row_counter = 0, rows_useful = 0; + size_t bytes = 0, last_bytes = 0; + + usec_t last_usec_from = 0; + usec_t last_usec_to = 0; + + WEVT_QUERY_STATUS status = WEVT_OK; + + facets_rows_begin(facets); + WEVT_EVENT e; + while (status == WEVT_OK && wevt_get_next_event(log, &e)) { + usec_t msg_ut = e.created_ns / NSEC_PER_USEC; + + if(unlikely(!msg_ut)) { + errors_no_timestamp++; + continue; + } + + if (unlikely(msg_ut < start_ut)) + continue; + + if (unlikely(msg_ut > stop_ut)) + break; + + if(likely(msg_ut > latest_msg_ut)) + latest_msg_ut = msg_ut; + + if(unlikely(!first_msg_ut)) { + first_msg_ut = msg_ut; + // lqs->c.query_file.first_msg_ut = msg_ut; + } + +// sampling_t sample = is_row_in_sample(log, lqs, src, msg_ut, +// FACETS_ANCHOR_DIRECTION_FORWARD, +// facets_row_candidate_to_keep(facets, msg_ut)); +// +// if(sample == SAMPLING_FULL) { + bytes += wevt_process_event(log, facets, src, &msg_ut, &e); + + // make sure each line gets a unique timestamp + if(unlikely(msg_ut >= last_usec_from && msg_ut <= last_usec_to)) + msg_ut = ++last_usec_to; + else + last_usec_from = last_usec_to = msg_ut; + + if(facets_row_finished(facets, msg_ut)) + rows_useful++; + + row_counter++; + if(unlikely((row_counter % FUNCTION_DATA_ONLY_CHECK_EVERY_ROWS) == 0 && + stop_when_full && + facets_rows(facets) >= lqs->rq.entries)) { + // stop the data only query + usec_t newest = facets_row_newest_ut(facets); + if(newest && msg_ut > (newest + lqs->anchor.delta_ut)) + break; + } + + if(unlikely(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0)) { + status = check_stop(lqs->cancelled, lqs->stop_monotonic_ut); + + if(status == WEVT_OK) { + lqs->c.rows_read += row_counter - last_row_counter; + last_row_counter = row_counter; + + lqs->c.bytes_read += bytes - last_bytes; + last_bytes = bytes; + + send_progress_update(lqs, row_counter, false); + } + } +// } +// else if(sample == SAMPLING_SKIP_FIELDS) +// facets_row_finished_unsampled(facets, msg_ut); +// else { +// sampling_update_running_query_file_estimates(facets, log, lqs, src, msg_ut, FACETS_ANCHOR_DIRECTION_FORWARD); +// break; +// } + } + + send_progress_update(lqs, row_counter, true); + lqs->c.rows_read += row_counter - last_row_counter; + lqs->c.bytes_read += bytes - last_bytes; + lqs->c.rows_useful += rows_useful; + + if(errors_no_timestamp) + netdata_log_error("WINDOWS-EVENTS: %zu events did not have timestamps", errors_no_timestamp); + + if(latest_msg_ut > lqs->last_modified) + lqs->last_modified = latest_msg_ut; + + wevt_query_done(log); + + return status; +} + +static WEVT_QUERY_STATUS wevt_query_one_channel( + WEVT_LOG *log, + BUFFER *wb, FACETS *facets, + LOGS_QUERY_SOURCE *src, + LOGS_QUERY_STATUS *lqs) { + + errno_clear(); + + WEVT_QUERY_STATUS status; + if(lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD) + status = wevt_query_forward(log, wb, facets, src, lqs); + else + status = wevt_query_backward(log, wb, facets, src, lqs); + + return status; +} + +static bool source_is_mine(LOGS_QUERY_SOURCE *src, LOGS_QUERY_STATUS *lqs) { + if( + // no source is requested + (lqs->rq.source_type == WEVTS_NONE && !lqs->rq.sources) || + + // matches our internal source types + (src->source_type & lqs->rq.source_type) || + + // matches the source name + (lqs->rq.sources && src->source && simple_pattern_matches(lqs->rq.sources, string2str(src->source))) || + + // matches the provider (providers start with a special prefix to avoid mix and match) + (lqs->rq.sources && src->provider && simple_pattern_matches(lqs->rq.sources, string2str(src->provider))) + + ) { + + if(!src->msg_last_ut) + // the file is not scanned yet, or the timestamps have not been updated, + // so we don't know if it can contribute or not - let's add it. + return true; + + usec_t anchor_delta = ANCHOR_DELTA_UT; + usec_t first_ut = src->msg_first_ut - anchor_delta; + usec_t last_ut = src->msg_last_ut + anchor_delta; + + if(last_ut >= lqs->rq.after_ut && first_ut <= lqs->rq.before_ut) + return true; + } + + return false; +} + +static int wevt_master_query(BUFFER *wb __maybe_unused, LOGS_QUERY_STATUS *lqs __maybe_unused) { + // make sure the sources list is updated + wevt_sources_scan(); + + lqs->c.query = wevt_generate_query_no_xpath(lqs, wb); + if(!lqs->c.query) + return rrd_call_function_error(wb, "failed to generate query", HTTP_RESP_INTERNAL_SERVER_ERROR); + + FACETS *facets = lqs->facets; + + WEVT_QUERY_STATUS status = WEVT_NO_CHANNEL_MATCHED; + + lqs->c.files_matched = 0; + lqs->c.file_working = 0; + lqs->c.rows_useful = 0; + lqs->c.rows_read = 0; + lqs->c.bytes_read = 0; + + size_t files_used = 0; + size_t files_max = dictionary_entries(wevt_sources); + const DICTIONARY_ITEM *file_items[files_max]; + + // count the files + bool files_are_newer = false; + LOGS_QUERY_SOURCE *src; + dfe_start_read(wevt_sources, src) { + if(!source_is_mine(src, lqs)) + continue; + + file_items[files_used++] = dictionary_acquired_item_dup(wevt_sources, src_dfe.item); + + if(src->msg_last_ut > lqs->rq.if_modified_since) + files_are_newer = true; + + lqs->c.progress.entries.total += src->entries; + } + dfe_done(jf); + + lqs->c.files_matched = files_used; + + if(lqs->rq.if_modified_since && !files_are_newer) { + // release the files + for(size_t f = 0; f < files_used ;f++) + dictionary_acquired_item_release(wevt_sources, file_items[f]); + + return rrd_call_function_error(wb, "not modified", HTTP_RESP_NOT_MODIFIED); + } + + // sort the files, so that they are optimal for facets + if(files_used >= 2) { + if (lqs->rq.direction == FACETS_ANCHOR_DIRECTION_BACKWARD) + qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *), + wevt_sources_dict_items_backward_compar); + else + qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *), + wevt_sources_dict_items_forward_compar); + } + + bool partial = false; + usec_t query_started_ut = now_monotonic_usec(); + usec_t started_ut = query_started_ut; + usec_t ended_ut = started_ut; + usec_t duration_ut, max_duration_ut = 0; + + WEVT_LOG *log = wevt_openlog6(query_has_fts(lqs) ? WEVT_QUERY_FTS : WEVT_QUERY_NORMAL); + if(!log) { + // release the files + for(size_t f = 0; f < files_used ;f++) + dictionary_acquired_item_release(wevt_sources, file_items[f]); + + netdata_log_error("WINDOWS EVENTS: cannot open windows event log"); + return rrd_call_function_error(wb, "cannot open windows events log", HTTP_RESP_INTERNAL_SERVER_ERROR); + } + + // sampling_query_init(lqs, facets); + + buffer_json_member_add_array(wb, "_channels"); + for(size_t f = 0; f < files_used ;f++) { + const char *fullname = dictionary_acquired_item_name(file_items[f]); + src = dictionary_acquired_item_value(file_items[f]); + + if(!source_is_mine(src, lqs)) + continue; + + started_ut = ended_ut; + + // do not even try to do the query if we expect it to pass the timeout + if(ended_ut + max_duration_ut * 3 >= *lqs->stop_monotonic_ut) { + partial = true; + status = WEVT_TIMED_OUT; + break; + } + + lqs->c.file_working++; + + size_t rows_useful = lqs->c.rows_useful; + size_t rows_read = lqs->c.rows_read; + size_t bytes_read = lqs->c.bytes_read; + size_t matches_setup_ut = lqs->c.matches_setup_ut; + + // sampling_file_init(lqs, src); + + lqs->c.progress.entries.current_query_total = src->entries; + WEVT_QUERY_STATUS tmp_status = wevt_query_one_channel(log, wb, facets, src, lqs); + + rows_useful = lqs->c.rows_useful - rows_useful; + rows_read = lqs->c.rows_read - rows_read; + bytes_read = lqs->c.bytes_read - bytes_read; + matches_setup_ut = lqs->c.matches_setup_ut - matches_setup_ut; + + ended_ut = now_monotonic_usec(); + duration_ut = ended_ut - started_ut; + + if(duration_ut > max_duration_ut) + max_duration_ut = duration_ut; + + buffer_json_add_array_item_object(wb); // channel source + { + // information about the file + buffer_json_member_add_string(wb, "_name", fullname); + buffer_json_member_add_uint64(wb, "_source_type", src->source_type); + buffer_json_member_add_string(wb, "_source", string2str(src->source)); + buffer_json_member_add_uint64(wb, "_msg_first_ut", src->msg_first_ut); + buffer_json_member_add_uint64(wb, "_msg_last_ut", src->msg_last_ut); + + // information about the current use of the file + buffer_json_member_add_uint64(wb, "duration_ut", ended_ut - started_ut); + buffer_json_member_add_uint64(wb, "rows_read", rows_read); + buffer_json_member_add_uint64(wb, "rows_useful", rows_useful); + buffer_json_member_add_double(wb, "rows_per_second", (double) rows_read / (double) duration_ut * (double) USEC_PER_SEC); + buffer_json_member_add_uint64(wb, "bytes_read", bytes_read); + buffer_json_member_add_double(wb, "bytes_per_second", (double) bytes_read / (double) duration_ut * (double) USEC_PER_SEC); + buffer_json_member_add_uint64(wb, "duration_matches_ut", matches_setup_ut); + + // if(lqs->rq.sampling) { + // buffer_json_member_add_object(wb, "_sampling"); + // { + // buffer_json_member_add_uint64(wb, "sampled", lqs->c.samples_per_file.sampled); + // buffer_json_member_add_uint64(wb, "unsampled", lqs->c.samples_per_file.unsampled); + // buffer_json_member_add_uint64(wb, "estimated", lqs->c.samples_per_file.estimated); + // } + // buffer_json_object_close(wb); // _sampling + // } + } + buffer_json_object_close(wb); // channel source + + bool stop = false; + switch(tmp_status) { + case WEVT_OK: + case WEVT_NO_CHANNEL_MATCHED: + status = (status == WEVT_OK) ? WEVT_OK : tmp_status; + break; + + case WEVT_FAILED_TO_OPEN: + case WEVT_FAILED_TO_SEEK: + partial = true; + if(status == WEVT_NO_CHANNEL_MATCHED) + status = tmp_status; + break; + + case WEVT_CANCELLED: + case WEVT_TIMED_OUT: + partial = true; + stop = true; + status = tmp_status; + break; + + case WEVT_NOT_MODIFIED: + internal_fatal(true, "this should never be returned here"); + break; + } + + if(stop) + break; + } + buffer_json_array_close(wb); // _channels + + // release the files + for(size_t f = 0; f < files_used ;f++) + dictionary_acquired_item_release(wevt_sources, file_items[f]); + + switch (status) { + case WEVT_OK: + if(lqs->rq.if_modified_since && !lqs->c.rows_useful) + return rrd_call_function_error(wb, "no useful logs, not modified", HTTP_RESP_NOT_MODIFIED); + break; + + case WEVT_TIMED_OUT: + case WEVT_NO_CHANNEL_MATCHED: + break; + + case WEVT_CANCELLED: + return rrd_call_function_error(wb, "client closed connection", HTTP_RESP_CLIENT_CLOSED_REQUEST); + + case WEVT_NOT_MODIFIED: + return rrd_call_function_error(wb, "not modified", HTTP_RESP_NOT_MODIFIED); + + case WEVT_FAILED_TO_OPEN: + return rrd_call_function_error(wb, "failed to open event log", HTTP_RESP_INTERNAL_SERVER_ERROR); + + case WEVT_FAILED_TO_SEEK: + return rrd_call_function_error(wb, "failed to execute event log query", HTTP_RESP_INTERNAL_SERVER_ERROR); + + default: + return rrd_call_function_error(wb, "unknown status", HTTP_RESP_INTERNAL_SERVER_ERROR); + } + + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); + buffer_json_member_add_boolean(wb, "partial", partial); + buffer_json_member_add_string(wb, "type", "table"); + + // build a message for the query + if(!lqs->rq.data_only) { + CLEAN_BUFFER *msg = buffer_create(0, NULL); + CLEAN_BUFFER *msg_description = buffer_create(0, NULL); + ND_LOG_FIELD_PRIORITY msg_priority = NDLP_INFO; + + // if(!journal_files_completed_once()) { + // buffer_strcat(msg, "Journals are still being scanned. "); + // buffer_strcat(msg_description + // , "LIBRARY SCAN: The journal files are still being scanned, you are probably viewing incomplete data. "); + // msg_priority = NDLP_WARNING; + // } + + if(partial) { + buffer_strcat(msg, "Query timed-out, incomplete data. "); + buffer_strcat(msg_description + , "QUERY TIMEOUT: The query timed out and may not include all the data of the selected window. "); + msg_priority = NDLP_WARNING; + } + + // if(lqs->c.samples.estimated || lqs->c.samples.unsampled) { + // double percent = (double) (lqs->c.samples.sampled * 100.0 / + // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled)); + // buffer_sprintf(msg, "%.2f%% real data", percent); + // buffer_sprintf(msg_description, "ACTUAL DATA: The filters counters reflect %0.2f%% of the data. ", percent); + // msg_priority = MIN(msg_priority, NDLP_NOTICE); + // } + // + // if(lqs->c.samples.unsampled) { + // double percent = (double) (lqs->c.samples.unsampled * 100.0 / + // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled)); + // buffer_sprintf(msg, ", %.2f%% unsampled", percent); + // buffer_sprintf(msg_description + // , "UNSAMPLED DATA: %0.2f%% of the events exist and have been counted, but their values have not been evaluated, so they are not included in the filters counters. " + // , percent); + // msg_priority = MIN(msg_priority, NDLP_NOTICE); + // } + // + // if(lqs->c.samples.estimated) { + // double percent = (double) (lqs->c.samples.estimated * 100.0 / + // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled)); + // buffer_sprintf(msg, ", %.2f%% estimated", percent); + // buffer_sprintf(msg_description + // , "ESTIMATED DATA: The query selected a large amount of data, so to avoid delaying too much, the presented data are estimated by %0.2f%%. " + // , percent); + // msg_priority = MIN(msg_priority, NDLP_NOTICE); + // } + + buffer_json_member_add_object(wb, "message"); + if(buffer_tostring(msg)) { + buffer_json_member_add_string(wb, "title", buffer_tostring(msg)); + buffer_json_member_add_string(wb, "description", buffer_tostring(msg_description)); + buffer_json_member_add_string(wb, "status", nd_log_id2priority(msg_priority)); + } + // else send an empty object if there is nothing to tell + buffer_json_object_close(wb); // message + } + + if(!lqs->rq.data_only) { + buffer_json_member_add_time_t(wb, "update_every", 1); + buffer_json_member_add_string(wb, "help", WEVT_FUNCTION_DESCRIPTION); + } + + if(!lqs->rq.data_only || lqs->rq.tail) + buffer_json_member_add_uint64(wb, "last_modified", lqs->last_modified); + + facets_sort_and_reorder_keys(facets); + facets_report(facets, wb, used_hashes_registry); + + wb->expires = now_realtime_sec() + (lqs->rq.data_only ? 3600 : 0); + buffer_json_member_add_time_t(wb, "expires", wb->expires); + + // if(lqs->rq.sampling) { + // buffer_json_member_add_object(wb, "_sampling"); + // { + // buffer_json_member_add_uint64(wb, "sampled", lqs->c.samples.sampled); + // buffer_json_member_add_uint64(wb, "unsampled", lqs->c.samples.unsampled); + // buffer_json_member_add_uint64(wb, "estimated", lqs->c.samples.estimated); + // } + // buffer_json_object_close(wb); // _sampling + // } + + wevt_closelog6(log); + + wb->content_type = CT_APPLICATION_JSON; + wb->response_code = HTTP_RESP_OK; + return wb->response_code; +} + +void function_windows_events(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, + BUFFER *payload, HTTP_ACCESS access __maybe_unused, + const char *source __maybe_unused, void *data __maybe_unused) { + bool have_slice = LQS_DEFAULT_SLICE_MODE; + + LOGS_QUERY_STATUS tmp_fqs = { + .facets = lqs_facets_create( + LQS_DEFAULT_ITEMS_PER_QUERY, + FACETS_OPTION_ALL_KEYS_FTS | FACETS_OPTION_HASH_IDS, + WEVT_ALWAYS_VISIBLE_KEYS, + WEVT_KEYS_INCLUDED_IN_FACETS, + WEVT_KEYS_EXCLUDED_FROM_FACETS, + have_slice), + + .rq = LOGS_QUERY_REQUEST_DEFAULTS(transaction, have_slice, FACETS_ANCHOR_DIRECTION_BACKWARD), + + .cancelled = cancelled, + .stop_monotonic_ut = stop_monotonic_ut, + }; + LOGS_QUERY_STATUS *lqs = &tmp_fqs; + + CLEAN_BUFFER *wb = lqs_create_output_buffer(); + + // ------------------------------------------------------------------------ + // parse the parameters + + if(lqs_request_parse_and_validate(lqs, wb, function, payload, have_slice, WEVT_FIELD_LEVEL)) { + wevt_register_fields(lqs); + + // ------------------------------------------------------------------------ + // add versions to the response + + buffer_json_wevt_versions(wb); + + // ------------------------------------------------------------------------ + // run the request + + if (lqs->rq.info) + lqs_info_response(wb, lqs->facets); + else { + wevt_master_query(wb, lqs); + if (wb->response_code == HTTP_RESP_OK) + buffer_json_finalize(wb); + } + } + + netdata_mutex_lock(&stdout_mutex); + pluginsd_function_result_to_stdout(transaction, wb); + netdata_mutex_unlock(&stdout_mutex); + + lqs_cleanup(lqs); +} + +int main(int argc __maybe_unused, char **argv __maybe_unused) { + nd_thread_tag_set("wevt.plugin"); + nd_log_initialize_for_external_plugins("windows-events.plugin"); + + // ------------------------------------------------------------------------ + // initialization + + wevt_sources_init(); + provider_cache_init(); + cached_sid_username_init(); + field_cache_init(); + + if(!EnableWindowsPrivilege(SE_SECURITY_NAME)) + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SECURITY_NAME); + + if(!EnableWindowsPrivilege(SE_BACKUP_NAME)) + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_BACKUP_NAME); + + if(!EnableWindowsPrivilege(SE_AUDIT_NAME)) + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_AUDIT_NAME); + + // ------------------------------------------------------------------------ + // debug + + if(argc >= 2 && strcmp(argv[argc - 1], "debug") == 0) { + wevt_sources_scan(); + + struct { + const char *func; + } array[] = { + { "windows-events after:-8640000 before:0 last:200 source:All" }, + //{ "windows-events after:-86400 before:0 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,JM3OPW3kHn6,H106l8MXSSr,HREiMN.4Ahu,ClaDGnYSQE7,ApYltST_icg,PtkRm91M0En data_only:false slice:true source:All" }, + //{ "windows-events after:1726055370 before:1726056270 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6,ClaDGnYSQE7,H106l8MXSSr,HREiMN.4Ahu data_only:false source:All HytMJ9kj82B:BlC24d5JBBV,PtVoyIuX.MU,HMj1B38kHTv KeCITtVD5AD:PY1JtCeWwSe,O9kz5J37nNl,JZoJURadhDb" }, + // { "windows-events after:1725636012 before:1726240812 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,JM3OPW3kHn6,H106l8MXSSr,HREiMN.4Ahu,ClaDGnYSQE7,ApYltST_icg,PtkRm91M0En data_only:false source:All PtkRm91M0En:LDzHbP5libb" }, + //{ "windows-events after:1725650386 before:1725736786 anchor:1725652420809461 direction:forward last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 if_modified_since:1725736649011085 data_only:true delta:true tail:true source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" }, + //{ "windows-events info after:1725650420 before:1725736820" }, + //{ "windows-events after:1725650420 before:1725736820 last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" }, + //{ "windows-events after:1725650430 before:1725736830 last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" }, + { NULL }, + }; + + for(int i = 0; array[i].func ;i++) { + bool cancelled = false; + usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC; + //char buf[] = "windows-events after:-86400 before:0 direction:backward last:200 data_only:false slice:true source:all"; + function_windows_events("123", (char *)array[i].func, &stop_monotonic_ut, &cancelled, NULL, HTTP_ACCESS_ALL, NULL, NULL); + } + printf("\n\nAll done!\n\n"); + fflush(stdout); + exit(1); + } + + // ------------------------------------------------------------------------ + // the event loop for functions + + struct functions_evloop_globals *wg = + functions_evloop_init(WINDOWS_EVENTS_WORKER_THREADS, "WEVT", &stdout_mutex, &plugin_should_exit); + + functions_evloop_add_function(wg, + WEVT_FUNCTION_NAME, + function_windows_events, + WINDOWS_EVENTS_DEFAULT_TIMEOUT, + NULL); + + // ------------------------------------------------------------------------ + // register functions to netdata + + netdata_mutex_lock(&stdout_mutex); + + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" "HTTP_ACCESS_FORMAT" %d\n", + WEVT_FUNCTION_NAME, WINDOWS_EVENTS_DEFAULT_TIMEOUT, WEVT_FUNCTION_DESCRIPTION, + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA), + RRDFUNCTIONS_PRIORITY_DEFAULT); + + fflush(stdout); + netdata_mutex_unlock(&stdout_mutex); + + // ------------------------------------------------------------------------ + + usec_t send_newline_ut = 0; + usec_t since_last_scan_ut = WINDOWS_EVENTS_SCAN_EVERY_USEC * 2; // something big to trigger scanning at start + usec_t since_last_providers_release_ut = 0; + const bool tty = isatty(fileno(stdout)) == 1; + + heartbeat_t hb; + heartbeat_init(&hb, USEC_PER_SEC); + while(!plugin_should_exit) { + + if(since_last_scan_ut > WINDOWS_EVENTS_SCAN_EVERY_USEC) { + wevt_sources_scan(); + since_last_scan_ut = 0; + } + + if(since_last_providers_release_ut > WINDOWS_EVENTS_RELEASE_PROVIDERS_HANDLES_EVERY_UT) { + providers_release_unused_handles(); + since_last_providers_release_ut = 0; + } + + usec_t dt_ut = heartbeat_next(&hb); + since_last_providers_release_ut += dt_ut; + since_last_scan_ut += dt_ut; + send_newline_ut += dt_ut; + + if(!tty && send_newline_ut > USEC_PER_SEC) { + send_newline_and_flush(&stdout_mutex); + send_newline_ut = 0; + } + } + + exit(0); +} |