youtubebeat/vendor/github.com/elastic/beats/winlogbeat/eventlog/eventlogging.go

305 lines
8.8 KiB
Go

// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.
// +build windows
package eventlog
import (
"fmt"
"syscall"
"time"
"github.com/joeshaw/multierror"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/winlogbeat/checkpoint"
"github.com/elastic/beats/winlogbeat/sys"
win "github.com/elastic/beats/winlogbeat/sys/eventlogging"
)
const (
// eventLoggingAPIName is the name used to identify the Event Logging API
// as both an event type and an API.
eventLoggingAPIName = "eventlogging"
)
var eventLoggingConfigKeys = append(commonConfigKeys, "ignore_older",
"read_buffer_size", "format_buffer_size")
type eventLoggingConfig struct {
ConfigCommon `config:",inline"`
IgnoreOlder time.Duration `config:"ignore_older"`
ReadBufferSize uint `config:"read_buffer_size" validate:"min=1"`
FormatBufferSize uint `config:"format_buffer_size" validate:"min=1"`
}
// Validate validates the eventLoggingConfig data and returns an error
// describing any problems or nil.
func (c *eventLoggingConfig) Validate() error {
var errs multierror.Errors
if c.Name == "" {
errs = append(errs, fmt.Errorf("event log is missing a 'name'"))
}
if c.ReadBufferSize > win.MaxEventBufferSize {
errs = append(errs, fmt.Errorf("'read_buffer_size' must be less than "+
"%d bytes", win.MaxEventBufferSize))
}
if c.FormatBufferSize > win.MaxFormatMessageBufferSize {
errs = append(errs, fmt.Errorf("'format_buffer_size' must be less than "+
"%d bytes", win.MaxFormatMessageBufferSize))
}
return errs.Err()
}
// Validate that eventLogging implements the EventLog interface.
var _ EventLog = &eventLogging{}
// eventLogging implements the EventLog interface for reading from the Event
// Logging API.
type eventLogging struct {
config eventLoggingConfig
name string // Name of the log that is opened.
handle win.Handle // Handle to the event log.
readBuf []byte // Buffer for reading in events.
formatBuf []byte // Buffer for formatting messages.
insertBuf win.StringInserts // Buffer for parsing insert strings.
handles *messageFilesCache // Cached mapping of source name to event message file handles.
logPrefix string // Prefix to add to all log entries.
recordNumber uint32 // First record number to read.
seek bool // Read should use seek.
ignoreFirst bool // Ignore first message returned from a read.
}
// Name returns the name of the event log (i.e. Application, Security, etc.).
func (l eventLogging) Name() string {
return l.name
}
func (l *eventLogging) Open(state checkpoint.EventLogState) error {
detailf("%s Open(recordNumber=%d) calling OpenEventLog(uncServerPath=, "+
"providerName=%s)", l.logPrefix, state.RecordNumber, l.name)
handle, err := win.OpenEventLog("", l.name)
if err != nil {
return err
}
numRecords, err := win.GetNumberOfEventLogRecords(handle)
if err != nil {
return err
}
var oldestRecord, newestRecord uint32
if numRecords > 0 {
l.recordNumber = uint32(state.RecordNumber)
l.seek = true
l.ignoreFirst = true
oldestRecord, err = win.GetOldestEventLogRecord(handle)
if err != nil {
return err
}
newestRecord = oldestRecord + numRecords - 1
if l.recordNumber < oldestRecord || l.recordNumber > newestRecord {
l.recordNumber = oldestRecord
l.ignoreFirst = false
}
} else {
l.recordNumber = 0
l.seek = false
l.ignoreFirst = false
}
logp.Info("%s contains %d records. Record number range [%d, %d]. Starting "+
"at %d (ignoringFirst=%t)", l.logPrefix, numRecords, oldestRecord,
newestRecord, l.recordNumber, l.ignoreFirst)
l.handle = handle
return nil
}
func (l *eventLogging) Read() ([]Record, error) {
flags := win.EVENTLOG_SEQUENTIAL_READ | win.EVENTLOG_FORWARDS_READ
if l.seek {
flags = win.EVENTLOG_SEEK_READ | win.EVENTLOG_FORWARDS_READ
l.seek = false
}
var numBytesRead int
err := retry(
func() error {
l.readBuf = l.readBuf[0:cap(l.readBuf)]
// TODO: Use number of bytes to grow the buffer size as needed.
var err error
numBytesRead, err = win.ReadEventLog(
l.handle,
flags,
l.recordNumber,
l.readBuf)
return err
},
l.readRetryErrorHandler)
if err != nil {
debugf("%s ReadEventLog returned error %v", l.logPrefix, err)
return readErrorHandler(err)
}
detailf("%s ReadEventLog read %d bytes", l.logPrefix, numBytesRead)
l.readBuf = l.readBuf[0:numBytesRead]
events, _, err := win.RenderEvents(
l.readBuf[:numBytesRead], 0, l.formatBuf, &l.insertBuf, l.handles.get)
if err != nil {
return nil, err
}
detailf("%s RenderEvents returned %d events", l.logPrefix, len(events))
records := make([]Record, 0, len(events))
for _, e := range events {
// The events do not contain the name of the event log so we must add
// the name of the log from which we are reading.
e.Channel = l.name
err = sys.PopulateAccount(&e.User)
if err != nil {
debugf("%s SID %s account lookup failed. %v", l.logPrefix,
e.User.Identifier, err)
}
records = append(records, Record{
API: eventLoggingAPIName,
Event: e,
Offset: checkpoint.EventLogState{
Name: l.name,
RecordNumber: e.RecordID,
Timestamp: e.TimeCreated.SystemTime,
},
})
}
if l.ignoreFirst && len(records) > 0 {
debugf("%s Ignoring first event with record ID %d", l.logPrefix,
records[0].RecordID)
records = records[1:]
l.ignoreFirst = false
}
records = filter(records, l.ignoreOlder)
debugf("%s Read() is returning %d records", l.logPrefix, len(records))
return records, nil
}
func (l *eventLogging) Close() error {
debugf("%s Closing handle", l.logPrefix)
return win.CloseEventLog(l.handle)
}
// readRetryErrorHandler handles errors returned from the readEventLog function
// by attempting to correct the error through closing and reopening the event
// log.
func (l *eventLogging) readRetryErrorHandler(err error) error {
incrementMetric(readErrors, err)
if errno, ok := err.(syscall.Errno); ok {
var reopen bool
switch errno {
case win.ERROR_EVENTLOG_FILE_CHANGED:
debugf("Re-opening event log because event log file was changed")
reopen = true
case win.ERROR_EVENTLOG_FILE_CORRUPT:
debugf("Re-opening event log because event log file is corrupt")
reopen = true
}
if reopen {
l.Close()
return l.Open(checkpoint.EventLogState{
Name: l.name,
RecordNumber: uint64(l.recordNumber),
})
}
}
return err
}
// readErrorHandler handles errors returned by the readEventLog function.
func readErrorHandler(err error) ([]Record, error) {
switch err {
case syscall.ERROR_HANDLE_EOF,
win.ERROR_EVENTLOG_FILE_CHANGED,
win.ERROR_EVENTLOG_FILE_CORRUPT:
return []Record{}, nil
}
return nil, err
}
// Filter returns a new slice holding only the elements of s that satisfy the
// predicate fn().
func filter(in []Record, fn func(*Record) bool) []Record {
var out []Record
for _, r := range in {
if fn(&r) {
out = append(out, r)
}
}
return out
}
// ignoreOlder is a filter predicate that checks the record timestamp and
// returns true if the event was not matched by the filter.
func (l *eventLogging) ignoreOlder(r *Record) bool {
if l.config.IgnoreOlder != 0 && time.Since(r.TimeCreated.SystemTime) > l.config.IgnoreOlder {
return false
}
return true
}
// newEventLogging creates and returns a new EventLog for reading event logs
// using the Event Logging API.
func newEventLogging(options *common.Config) (EventLog, error) {
c := eventLoggingConfig{
ReadBufferSize: win.MaxEventBufferSize,
FormatBufferSize: win.MaxFormatMessageBufferSize,
}
if err := readConfig(options, &c, eventLoggingConfigKeys); err != nil {
return nil, err
}
return &eventLogging{
config: c,
name: c.Name,
handles: newMessageFilesCache(c.Name, win.QueryEventMessageFiles,
win.FreeLibrary),
logPrefix: fmt.Sprintf("EventLogging[%s]", c.Name),
readBuf: make([]byte, 0, c.ReadBufferSize),
formatBuf: make([]byte, c.FormatBufferSize),
}, nil
}
func init() {
// Register eventlogging API if it is available.
available, _ := win.IsAvailable()
if available {
Register(eventLoggingAPIName, 1, newEventLogging, nil)
}
}