/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Fluent Bit * ========== * Copyright (C) 2015-2022 The Fluent Bit Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "winlog.h" struct winlog_channel *winlog_open(const char *channel) { struct winlog_channel *ch; ch = flb_calloc(1, sizeof(struct winlog_channel)); if (!ch) { flb_errno(); return NULL; } ch->name = flb_strdup(channel); if (!ch->name) { flb_errno(); flb_free(ch); return NULL; } ch->h = OpenEventLogA(NULL, channel); if (!ch->h) { flb_error("[in_winlog] cannot open '%s' (%i)", channel, GetLastError()); flb_free(ch->name); flb_free(ch); return NULL; } return ch; } void winlog_close(struct winlog_channel *ch) { flb_free(ch->name); CloseEventLog(ch->h); flb_free(ch); } /* * This routine is called when Windows Event Log was cleared * while reading (e.g. running Clear-EventLog on PowerShell). * * In such a case, the only neat thing to do is to reopen the * channel and start reading from the beginning. */ int winlog_on_cleared(struct winlog_channel *ch) { HANDLE h; h = OpenEventLogA(NULL, ch->name); if (!h) { flb_error("[in_winlog] cannot open '%s' (%i)", ch->name, GetLastError()); return -1; } if (ch->h) { CloseEventLog(ch->h); } ch->h = h; ch->seek = 0; return 0; } /* * ReadEventLog() has a known bug that SEEK_READ fails when the log file * is too big. * * winlog_seek() is a workaround for the issue, which emulates seek * by reading the stream until it reaches the target record. * * https://support.microsoft.com/en-hk/help/177199/ */ static int winlog_seek(struct winlog_channel *ch, char *buf, unsigned int size, unsigned int *read) { char *p; char *end; PEVENTLOGRECORD evt; ch->seek = 0; while (1) { if (winlog_read(ch, buf, size, read)) { return -1; } if (*read == 0) { flb_trace("[in_winlog] seek '%s' to EOF", ch->name); return 0; } p = buf; end = buf + *read; while (p < end) { evt = (PEVENTLOGRECORD) p; /* If the record is newer than the last record we've read, * stop immediately. */ if (evt->TimeWritten > ch->time_written) { *read = (end - p); memmove(buf, p, *read); flb_trace("[in_winlog] seek '%s' to RecordNumber=%u (time)", ch->name, evt->RecordNumber); return 0; } if (evt->TimeWritten == ch->time_written) { /* If the record was written at the same time, compare * the record number. * * Note! Since Windows would reset RecordNumber occasionally, * this comparison is not completely reliable. */ if (evt->RecordNumber > ch->record_number) { *read = (end - p); memmove(buf, p, *read); flb_trace("[in_winlog] seek '%s' to RecordNumber=%u", ch->name, evt->RecordNumber); return 0; } } p += evt->Length; } } } /* * Read from an open Windows Event Log channel. */ int winlog_read(struct winlog_channel *ch, char *buf, unsigned int size, unsigned int *read) { unsigned int flags; unsigned int req; unsigned int err; if (ch->seek) { flags = EVENTLOG_SEEK_READ; } else { flags = EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ; } /* * Note: ReadEventLogW() ignores `ch->record_number` (dwRecordOffset) * if EVENTLOG_SEEK_READ is not set. */ if (!ReadEventLogW(ch->h, flags, ch->record_number, buf, size, read, &req)) { switch (err = GetLastError()) { case ERROR_HANDLE_EOF: break; case ERROR_INVALID_PARAMETER: return winlog_seek(ch, buf, size, read); case ERROR_EVENTLOG_FILE_CHANGED: flb_info("[in_winlog] channel '%s' is cleared. reopen it.", ch->name); return winlog_on_cleared(ch); default: flb_error("[in_winlog] cannot read '%s' (%i)", ch->name, err); return -1; } } ch->seek = 0; return 0; } /* * Open multiple channels at once. The return value is a linked * list of window_channel objects. * * "channels" are comma-separated names like "Setup,Security". */ struct mk_list *winlog_open_all(const char *channels) { char *tmp; char *channel; char *state; struct winlog_channel *ch; struct mk_list *list; tmp = flb_strdup(channels); if (!tmp) { flb_errno(); return NULL; } list = flb_malloc(sizeof(struct mk_list)); if (!list) { flb_errno(); flb_free(tmp); return NULL; } mk_list_init(list); channel = strtok_s(tmp , ",", &state); while (channel) { ch = winlog_open(channel); if (!ch) { flb_free(tmp); winlog_close_all(list); return NULL; } mk_list_add(&ch->_head, list); channel = strtok_s(NULL, ",", &state); } flb_free(tmp); return list; } void winlog_close_all(struct mk_list *list) { struct winlog_channel *ch; struct mk_list *head; struct mk_list *tmp; mk_list_foreach_safe(head, tmp, list) { ch = mk_list_entry(head, struct winlog_channel, _head); mk_list_del(&ch->_head); winlog_close(ch); } flb_free(list); } /* * Callback function for flb_sqldb_query(). */ static int winlog_sqlite_callback(void *data, int argc, char **argv, char **cols) { struct winlog_sqlite_record *p = data; p->name = argv[0]; p->record_number = (unsigned int) strtoul(argv[1], NULL, 10); p->time_written = (unsigned int) strtoul(argv[2], NULL, 10); p->created = (unsigned int) strtoul(argv[3], NULL, 10); return 0; } /* * Load the read offset from SQLite DB. */ int winlog_sqlite_load(struct winlog_channel *ch, struct flb_sqldb *db) { int ret; char query[1024]; struct winlog_sqlite_record record = {0}; snprintf(query, sizeof(query) - 1, SQL_GET_CHANNEL, ch->name); ret = flb_sqldb_query(db, query, winlog_sqlite_callback, &record); if (ret == FLB_ERROR) { return -1; } if (record.name) { ch->record_number = record.record_number; ch->time_written = record.time_written; ch->seek = 1; } return 0; } /* * Save the read offset into SQLite DB. */ int winlog_sqlite_save(struct winlog_channel *ch, struct flb_sqldb *db) { int ret; char query[1024]; snprintf(query, sizeof(query) - 1, SQL_UPDATE_CHANNEL, ch->name, ch->record_number, ch->time_written, time(NULL)); ret = flb_sqldb_query(db, query, NULL, NULL); if (ret == FLB_ERROR) { return -1; } return 0; }