xref: /reactos/sdk/lib/evtlib/evtlib.c (revision 8a978a17)
1 /*
2  * PROJECT:         ReactOS EventLog File Library
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            sdk/lib/evtlib/evtlib.c
5  * PURPOSE:         Provides functionality for reading and writing
6  *                  EventLog files in the NT <= 5.2 (.evt) format.
7  * PROGRAMMERS:     Copyright 2005 Saveliy Tretiakov
8  *                  Michael Martin
9  *                  Hermes Belusca-Maito
10  */
11 
12 /* INCLUDES ******************************************************************/
13 
14 #include "evtlib.h"
15 
16 #define NDEBUG
17 #include <debug.h>
18 
19 #define EVTLTRACE(...)  DPRINT("EvtLib: " __VA_ARGS__)
20 // Once things become stabilized enough, replace all the EVTLTRACE1 by EVTLTRACE
21 #define EVTLTRACE1(...)  DPRINT1("EvtLib: " __VA_ARGS__)
22 
23 
24 /* GLOBALS *******************************************************************/
25 
26 static const EVENTLOGEOF EOFRecord =
27 {
28     sizeof(EOFRecord),
29     0x11111111, 0x22222222, 0x33333333, 0x44444444,
30     0, 0, 0, 0,
31     sizeof(EOFRecord)
32 };
33 
34 /* HELPER FUNCTIONS **********************************************************/
35 
36 static NTSTATUS
37 ReadLogBuffer(
38     IN  PEVTLOGFILE LogFile,
39     OUT PVOID   Buffer,
40     IN  SIZE_T  Length,
41     OUT PSIZE_T ReadLength OPTIONAL,
42     IN  PLARGE_INTEGER ByteOffset,
43     OUT PLARGE_INTEGER NextOffset OPTIONAL)
44 {
45     NTSTATUS Status;
46     LARGE_INTEGER FileOffset;
47     SIZE_T BufSize;
48     SIZE_T ReadBufLength = 0, OldReadBufLength;
49 
50     ASSERT(LogFile->CurrentSize <= LogFile->Header.MaxSize);
51     ASSERT(ByteOffset->QuadPart <= LogFile->CurrentSize);
52 
53     if (ReadLength)
54         *ReadLength = 0;
55 
56     if (NextOffset)
57         NextOffset->QuadPart = 0LL;
58 
59     /* Read the first part of the buffer */
60     FileOffset = *ByteOffset;
61     BufSize = min(Length, LogFile->CurrentSize - FileOffset.QuadPart);
62 
63     Status = LogFile->FileRead(LogFile,
64                                &FileOffset,
65                                Buffer,
66                                BufSize,
67                                &ReadBufLength);
68     if (!NT_SUCCESS(Status))
69     {
70         EVTLTRACE("FileRead() failed (Status 0x%08lx)\n", Status);
71         return Status;
72     }
73 
74     if (Length > BufSize)
75     {
76         OldReadBufLength = ReadBufLength;
77 
78         /*
79          * The buffer was splitted in two, its second part
80          * is to be read at the beginning of the log.
81          */
82         Buffer = (PVOID)((ULONG_PTR)Buffer + BufSize);
83         BufSize = Length - BufSize;
84         FileOffset.QuadPart = sizeof(EVENTLOGHEADER);
85 
86         Status = LogFile->FileRead(LogFile,
87                                    &FileOffset,
88                                    Buffer,
89                                    BufSize,
90                                    &ReadBufLength);
91         if (!NT_SUCCESS(Status))
92         {
93             EVTLTRACE("FileRead() failed (Status 0x%08lx)\n", Status);
94             return Status;
95         }
96         /* Add the read number of bytes from the first read */
97         ReadBufLength += OldReadBufLength;
98     }
99 
100     if (ReadLength)
101         *ReadLength = ReadBufLength;
102 
103     /* We return the offset just after the end of the read buffer */
104     if (NextOffset)
105         NextOffset->QuadPart = FileOffset.QuadPart + BufSize;
106 
107     return Status;
108 }
109 
110 static NTSTATUS
111 WriteLogBuffer(
112     IN  PEVTLOGFILE LogFile,
113     IN  PVOID   Buffer,
114     IN  SIZE_T  Length,
115     OUT PSIZE_T WrittenLength OPTIONAL,
116     IN  PLARGE_INTEGER ByteOffset,
117     OUT PLARGE_INTEGER NextOffset OPTIONAL)
118 {
119     NTSTATUS Status;
120     LARGE_INTEGER FileOffset;
121     SIZE_T BufSize;
122     SIZE_T WrittenBufLength = 0, OldWrittenBufLength;
123 
124     /* We must have write access to the log file */
125     ASSERT(!LogFile->ReadOnly);
126 
127     /*
128      * It is expected that the log file is already correctly expanded
129      * before we can write in it. Therefore the following assertions hold.
130      */
131     ASSERT(LogFile->CurrentSize <= LogFile->Header.MaxSize);
132     ASSERT(ByteOffset->QuadPart <= LogFile->CurrentSize);
133 
134     if (WrittenLength)
135         *WrittenLength = 0;
136 
137     if (NextOffset)
138         NextOffset->QuadPart = 0LL;
139 
140     /* Write the first part of the buffer */
141     FileOffset = *ByteOffset;
142     BufSize = min(Length, LogFile->CurrentSize - FileOffset.QuadPart);
143 
144     Status = LogFile->FileWrite(LogFile,
145                                 &FileOffset,
146                                 Buffer,
147                                 BufSize,
148                                 &WrittenBufLength);
149     if (!NT_SUCCESS(Status))
150     {
151         EVTLTRACE("FileWrite() failed (Status 0x%08lx)\n", Status);
152         return Status;
153     }
154 
155     if (Length > BufSize)
156     {
157         OldWrittenBufLength = WrittenBufLength;
158 
159         /*
160          * The buffer was splitted in two, its second part
161          * is written at the beginning of the log.
162          */
163         Buffer = (PVOID)((ULONG_PTR)Buffer + BufSize);
164         BufSize = Length - BufSize;
165         FileOffset.QuadPart = sizeof(EVENTLOGHEADER);
166 
167         Status = LogFile->FileWrite(LogFile,
168                                     &FileOffset,
169                                     Buffer,
170                                     BufSize,
171                                     &WrittenBufLength);
172         if (!NT_SUCCESS(Status))
173         {
174             EVTLTRACE("FileWrite() failed (Status 0x%08lx)\n", Status);
175             return Status;
176         }
177         /* Add the written number of bytes from the first write */
178         WrittenBufLength += OldWrittenBufLength;
179 
180         /* The log wraps */
181         LogFile->Header.Flags |= ELF_LOGFILE_HEADER_WRAP;
182     }
183 
184     if (WrittenLength)
185         *WrittenLength = WrittenBufLength;
186 
187     /* We return the offset just after the end of the written buffer */
188     if (NextOffset)
189         NextOffset->QuadPart = FileOffset.QuadPart + BufSize;
190 
191     return Status;
192 }
193 
194 
195 /* Returns 0 if nothing is found */
196 static ULONG
197 ElfpOffsetByNumber(
198     IN PEVTLOGFILE LogFile,
199     IN ULONG RecordNumber)
200 {
201     UINT i;
202 
203     for (i = 0; i < LogFile->OffsetInfoNext; i++)
204     {
205         if (LogFile->OffsetInfo[i].EventNumber == RecordNumber)
206             return LogFile->OffsetInfo[i].EventOffset;
207     }
208     return 0;
209 }
210 
211 #define OFFSET_INFO_INCREMENT   64
212 
213 static BOOL
214 ElfpAddOffsetInformation(
215     IN PEVTLOGFILE LogFile,
216     IN ULONG ulNumber,
217     IN ULONG ulOffset)
218 {
219     PVOID NewOffsetInfo;
220 
221     if (LogFile->OffsetInfoNext == LogFile->OffsetInfoSize)
222     {
223         /* Allocate a new offset table */
224         NewOffsetInfo = LogFile->Allocate((LogFile->OffsetInfoSize + OFFSET_INFO_INCREMENT) *
225                                               sizeof(EVENT_OFFSET_INFO),
226                                           HEAP_ZERO_MEMORY,
227                                           TAG_ELF);
228         if (!NewOffsetInfo)
229         {
230             EVTLTRACE1("Cannot reallocate heap.\n");
231             return FALSE;
232         }
233 
234         /* Free the old offset table and use the new one */
235         if (LogFile->OffsetInfo)
236         {
237             /* Copy the handles from the old table to the new one */
238             RtlCopyMemory(NewOffsetInfo,
239                           LogFile->OffsetInfo,
240                           LogFile->OffsetInfoSize * sizeof(EVENT_OFFSET_INFO));
241             LogFile->Free(LogFile->OffsetInfo, 0, TAG_ELF);
242         }
243         LogFile->OffsetInfo = (PEVENT_OFFSET_INFO)NewOffsetInfo;
244         LogFile->OffsetInfoSize += OFFSET_INFO_INCREMENT;
245     }
246 
247     LogFile->OffsetInfo[LogFile->OffsetInfoNext].EventNumber = ulNumber;
248     LogFile->OffsetInfo[LogFile->OffsetInfoNext].EventOffset = ulOffset;
249     LogFile->OffsetInfoNext++;
250 
251     return TRUE;
252 }
253 
254 static BOOL
255 ElfpDeleteOffsetInformation(
256     IN PEVTLOGFILE LogFile,
257     IN ULONG ulNumberMin,
258     IN ULONG ulNumberMax)
259 {
260     UINT i;
261 
262     if (ulNumberMin > ulNumberMax)
263         return FALSE;
264 
265     /* Remove records ulNumberMin to ulNumberMax inclusive */
266     while (ulNumberMin <= ulNumberMax)
267     {
268         /*
269          * As the offset information is listed in increasing order, and we want
270          * to keep the list without holes, we demand that ulNumberMin is the first
271          * element in the list.
272          */
273         if (ulNumberMin != LogFile->OffsetInfo[0].EventNumber)
274             return FALSE;
275 
276         /*
277          * RtlMoveMemory(&LogFile->OffsetInfo[0],
278          *               &LogFile->OffsetInfo[1],
279          *               sizeof(EVENT_OFFSET_INFO) * (LogFile->OffsetInfoNext - 1));
280          */
281         for (i = 0; i < LogFile->OffsetInfoNext - 1; i++)
282         {
283             LogFile->OffsetInfo[i].EventNumber = LogFile->OffsetInfo[i + 1].EventNumber;
284             LogFile->OffsetInfo[i].EventOffset = LogFile->OffsetInfo[i + 1].EventOffset;
285         }
286         LogFile->OffsetInfoNext--;
287 
288         /* Go to the next offset information */
289         ulNumberMin++;
290     }
291 
292     return TRUE;
293 }
294 
295 
296 static NTSTATUS
297 ElfpInitNewFile(
298     IN PEVTLOGFILE LogFile,
299     IN ULONG FileSize,
300     IN ULONG MaxSize,
301     IN ULONG Retention)
302 {
303     NTSTATUS Status;
304     LARGE_INTEGER FileOffset;
305     SIZE_T WrittenLength;
306     EVENTLOGEOF EofRec;
307 
308     /* Initialize the event log header */
309     RtlZeroMemory(&LogFile->Header, sizeof(EVENTLOGHEADER));
310 
311     LogFile->Header.HeaderSize = sizeof(EVENTLOGHEADER);
312     LogFile->Header.Signature  = LOGFILE_SIGNATURE;
313     LogFile->Header.MajorVersion = MAJORVER;
314     LogFile->Header.MinorVersion = MINORVER;
315 
316     /* Set the offset to the oldest record */
317     LogFile->Header.StartOffset = sizeof(EVENTLOGHEADER);
318     /* Set the offset to the ELF_EOF_RECORD */
319     LogFile->Header.EndOffset = sizeof(EVENTLOGHEADER);
320     /* Set the number of the next record that will be added */
321     LogFile->Header.CurrentRecordNumber = 1;
322     /* The event log is empty, there is no record so far */
323     LogFile->Header.OldestRecordNumber = 0;
324 
325     // FIXME: Windows' EventLog log file sizes are always multiple of 64kB
326     // but that does not mean the real log size is == file size.
327 
328     /* Round MaxSize to be a multiple of ULONG (normally on Windows: multiple of 64 kB) */
329     LogFile->Header.MaxSize = ROUND_UP(MaxSize, sizeof(ULONG));
330     LogFile->CurrentSize = LogFile->Header.MaxSize; // or: FileSize ??
331     LogFile->FileSetSize(LogFile, LogFile->CurrentSize, 0);
332 
333     LogFile->Header.Flags = 0;
334     LogFile->Header.Retention = Retention;
335     LogFile->Header.EndHeaderSize = sizeof(EVENTLOGHEADER);
336 
337     /* Write the header */
338     FileOffset.QuadPart = 0LL;
339     Status = LogFile->FileWrite(LogFile,
340                                 &FileOffset,
341                                 &LogFile->Header,
342                                 sizeof(EVENTLOGHEADER),
343                                 &WrittenLength);
344     if (!NT_SUCCESS(Status))
345     {
346         EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
347         return Status;
348     }
349 
350     /* Initialize the ELF_EOF_RECORD and write it */
351     RtlCopyMemory(&EofRec, &EOFRecord, sizeof(EOFRecord));
352     EofRec.BeginRecord = LogFile->Header.StartOffset;
353     EofRec.EndRecord   = LogFile->Header.EndOffset;
354     EofRec.CurrentRecordNumber = LogFile->Header.CurrentRecordNumber;
355     EofRec.OldestRecordNumber  = LogFile->Header.OldestRecordNumber;
356 
357     Status = LogFile->FileWrite(LogFile,
358                                 NULL,
359                                 &EofRec,
360                                 sizeof(EofRec),
361                                 &WrittenLength);
362     if (!NT_SUCCESS(Status))
363     {
364         EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
365         return Status;
366     }
367 
368     Status = LogFile->FileFlush(LogFile, NULL, 0);
369     if (!NT_SUCCESS(Status))
370     {
371         EVTLTRACE1("FileFlush() failed (Status 0x%08lx)\n", Status);
372         return Status;
373     }
374 
375     return STATUS_SUCCESS;
376 }
377 
378 static NTSTATUS
379 ElfpInitExistingFile(
380     IN PEVTLOGFILE LogFile,
381     IN ULONG FileSize,
382     // IN ULONG MaxSize,
383     IN ULONG Retention)
384 {
385     NTSTATUS Status;
386     LARGE_INTEGER FileOffset, NextOffset;
387     SIZE_T ReadLength;
388     ULONG RecordNumber = 0;
389     ULONG RecOffset;
390     PULONG pRecSize2;
391     EVENTLOGEOF EofRec;
392     EVENTLOGRECORD RecBuf;
393     PEVENTLOGRECORD pRecBuf;
394     BOOLEAN Wrapping = FALSE;
395     BOOLEAN IsLogDirty = FALSE;
396 
397     /* Read the log header */
398     FileOffset.QuadPart = 0LL;
399     Status = LogFile->FileRead(LogFile,
400                                &FileOffset,
401                                &LogFile->Header,
402                                sizeof(EVENTLOGHEADER),
403                                &ReadLength);
404     if (!NT_SUCCESS(Status))
405     {
406         EVTLTRACE1("FileRead() failed (Status 0x%08lx)\n", Status);
407         return STATUS_EVENTLOG_FILE_CORRUPT; // return Status;
408     }
409     if (ReadLength != sizeof(EVENTLOGHEADER))
410     {
411         EVTLTRACE("Invalid file `%wZ'.\n", &LogFile->FileName);
412         return STATUS_EVENTLOG_FILE_CORRUPT;
413     }
414 
415     /* Header validity checks */
416 
417     if (LogFile->Header.HeaderSize != sizeof(EVENTLOGHEADER) ||
418         LogFile->Header.EndHeaderSize != sizeof(EVENTLOGHEADER))
419     {
420         EVTLTRACE("Invalid header size in `%wZ'.\n", &LogFile->FileName);
421         return STATUS_EVENTLOG_FILE_CORRUPT;
422     }
423 
424     if (LogFile->Header.Signature != LOGFILE_SIGNATURE)
425     {
426         EVTLTRACE("Invalid signature %x in `%wZ'.\n",
427                LogFile->Header.Signature, &LogFile->FileName);
428         return STATUS_EVENTLOG_FILE_CORRUPT;
429     }
430 
431     IsLogDirty = (LogFile->Header.Flags & ELF_LOGFILE_HEADER_DIRTY);
432 
433     /* If the log is read-only (e.g. a backup log) and is dirty, then it is corrupted */
434     if (LogFile->ReadOnly && IsLogDirty)
435     {
436         EVTLTRACE("Read-only log `%wZ' is dirty.\n", &LogFile->FileName);
437         return STATUS_EVENTLOG_FILE_CORRUPT;
438     }
439 
440     LogFile->CurrentSize = FileSize;
441     // FIXME!! What to do? And what to do if the MaxSize from the registry
442     // is strictly less than the CurrentSize?? Should we "reduce" the log size
443     // by clearing it completely??
444     // --> ANSWER: Save the new MaxSize somewhere, and only when the log is
445     //     being cleared, use the new MaxSize to resize (ie. shrink) it.
446     // LogFile->FileSetSize(LogFile, LogFile->CurrentSize, 0);
447 
448     /* Adjust the log maximum size if needed */
449     if (LogFile->CurrentSize > LogFile->Header.MaxSize)
450         LogFile->Header.MaxSize = LogFile->CurrentSize;
451 
452     /*
453      * Reset the log retention value. The value stored
454      * in the log file is just for information purposes.
455      */
456     LogFile->Header.Retention = Retention;
457 
458     /*
459      * For a non-read-only dirty log, the most up-to-date information about
460      * the Start/End offsets and the Oldest and Current event record numbers
461      * are found in the EOF record. We need to locate the EOF record without
462      * relying on the log header's EndOffset, then patch the log header with
463      * the values from the EOF record.
464      */
465     if ((LogFile->Header.EndOffset >= sizeof(EVENTLOGHEADER)) &&
466         (LogFile->Header.EndOffset <  LogFile->CurrentSize) &&
467         (LogFile->Header.EndOffset & 3) == 0) // EndOffset % sizeof(ULONG) == 0
468     {
469         /* The header EOF offset may be valid, try to start with it */
470         RecOffset = LogFile->Header.EndOffset;
471     }
472     else
473     {
474         /* The header EOF offset could not be valid, so start from the beginning */
475         RecOffset = sizeof(EVENTLOGHEADER);
476     }
477 
478     FileOffset.QuadPart = RecOffset;
479     Wrapping = FALSE;
480 
481     for (;;)
482     {
483         if (Wrapping && FileOffset.QuadPart >= RecOffset)
484         {
485             EVTLTRACE1("EOF record not found!\n");
486             return STATUS_EVENTLOG_FILE_CORRUPT;
487         }
488 
489         /* Attempt to read the fixed part of an EVENTLOGEOF (may wrap) */
490         Status = ReadLogBuffer(LogFile,
491                                &EofRec,
492                                EVENTLOGEOF_SIZE_FIXED,
493                                &ReadLength,
494                                &FileOffset,
495                                NULL);
496         if (!NT_SUCCESS(Status))
497         {
498             EVTLTRACE1("ReadLogBuffer failed (Status 0x%08lx)\n", Status);
499             return STATUS_EVENTLOG_FILE_CORRUPT;
500         }
501         if (ReadLength != EVENTLOGEOF_SIZE_FIXED)
502         {
503             EVTLTRACE1("Cannot read at most an EOF record!\n");
504             return STATUS_EVENTLOG_FILE_CORRUPT;
505         }
506 
507         /* Is it an EVENTLOGEOF record? */
508         if (RtlCompareMemory(&EofRec, &EOFRecord, EVENTLOGEOF_SIZE_FIXED) == EVENTLOGEOF_SIZE_FIXED)
509         {
510             DPRINT("Found EOF record at %llx\n", FileOffset.QuadPart);
511 
512             /* Got it! Break the loop and continue */
513             break;
514         }
515 
516         /* No, continue looping */
517         if (*(PULONG)((ULONG_PTR)&EofRec + sizeof(ULONG)) == *(PULONG)(&EOFRecord))
518             FileOffset.QuadPart += sizeof(ULONG);
519         else
520         if (*(PULONG)((ULONG_PTR)&EofRec + 2*sizeof(ULONG)) == *(PULONG)(&EOFRecord))
521             FileOffset.QuadPart += 2*sizeof(ULONG);
522         else
523         if (*(PULONG)((ULONG_PTR)&EofRec + 3*sizeof(ULONG)) == *(PULONG)(&EOFRecord))
524             FileOffset.QuadPart += 3*sizeof(ULONG);
525         else
526         if (*(PULONG)((ULONG_PTR)&EofRec + 4*sizeof(ULONG)) == *(PULONG)(&EOFRecord))
527             FileOffset.QuadPart += 4*sizeof(ULONG);
528         else
529             FileOffset.QuadPart += 5*sizeof(ULONG); // EVENTLOGEOF_SIZE_FIXED
530 
531         if (FileOffset.QuadPart >= LogFile->CurrentSize /* LogFile->Header.MaxSize */)
532         {
533             /* Wrap the offset */
534             FileOffset.QuadPart -= LogFile->CurrentSize /* LogFile->Header.MaxSize */ - sizeof(EVENTLOGHEADER);
535             Wrapping = TRUE;
536         }
537     }
538     /*
539      * The only way to be there is to have found a valid EOF record.
540      * Otherwise the previous loop has failed and STATUS_EVENTLOG_FILE_CORRUPT
541      * was returned.
542      */
543 
544     /* Read the full EVENTLOGEOF (may wrap) and validate it */
545     Status = ReadLogBuffer(LogFile,
546                            &EofRec,
547                            sizeof(EofRec),
548                            &ReadLength,
549                            &FileOffset,
550                            NULL);
551     if (!NT_SUCCESS(Status))
552     {
553         EVTLTRACE1("ReadLogBuffer failed (Status 0x%08lx)\n", Status);
554         return STATUS_EVENTLOG_FILE_CORRUPT;
555     }
556     if (ReadLength != sizeof(EofRec))
557     {
558         EVTLTRACE1("Cannot read the full EOF record!\n");
559         return STATUS_EVENTLOG_FILE_CORRUPT;
560     }
561 
562     /* Complete validity checks */
563     if ((EofRec.RecordSizeEnd != EofRec.RecordSizeBeginning) ||
564         (EofRec.EndRecord != FileOffset.QuadPart))
565     {
566         DPRINT1("EOF record %llx is corrupted (0x%x vs. 0x%x ; 0x%x vs. 0x%llx), expected 0x%x 0x%x!\n",
567                 FileOffset.QuadPart,
568                 EofRec.RecordSizeEnd, EofRec.RecordSizeBeginning,
569                 EofRec.EndRecord, FileOffset.QuadPart,
570                 EOFRecord.RecordSizeEnd, EOFRecord.RecordSizeBeginning);
571         DPRINT1("RecordSizeEnd = 0x%x\n", EofRec.RecordSizeEnd);
572         DPRINT1("RecordSizeBeginning = 0x%x\n", EofRec.RecordSizeBeginning);
573         DPRINT1("EndRecord = 0x%x\n", EofRec.EndRecord);
574         return STATUS_EVENTLOG_FILE_CORRUPT;
575     }
576 
577     /* The EOF record is valid, break the loop and continue */
578 
579     /* If the log is not dirty, the header values should correspond to the EOF ones */
580     if (!IsLogDirty)
581     {
582         if ( (LogFile->Header.StartOffset != EofRec.BeginRecord) ||
583              (LogFile->Header.EndOffset   != EofRec.EndRecord)   ||
584              (LogFile->Header.CurrentRecordNumber != EofRec.CurrentRecordNumber) ||
585              (LogFile->Header.OldestRecordNumber  != EofRec.OldestRecordNumber) )
586         {
587             DPRINT1("\n"
588                     "Log header or EOF record is corrupted:\n"
589                     "    StartOffset: 0x%x, expected 0x%x; EndOffset: 0x%x, expected 0x%x;\n"
590                     "    CurrentRecordNumber: %d, expected %d; OldestRecordNumber: %d, expected %d.\n",
591                     LogFile->Header.StartOffset, EofRec.BeginRecord,
592                     LogFile->Header.EndOffset  , EofRec.EndRecord,
593                     LogFile->Header.CurrentRecordNumber, EofRec.CurrentRecordNumber,
594                     LogFile->Header.OldestRecordNumber , EofRec.OldestRecordNumber);
595 
596             return STATUS_EVENTLOG_FILE_CORRUPT;
597         }
598     }
599 
600     /* If the log is dirty, patch the log header with the values from the EOF record */
601     if (!LogFile->ReadOnly && IsLogDirty)
602     {
603         LogFile->Header.StartOffset = EofRec.BeginRecord;
604         LogFile->Header.EndOffset   = EofRec.EndRecord;
605         LogFile->Header.CurrentRecordNumber = EofRec.CurrentRecordNumber;
606         LogFile->Header.OldestRecordNumber  = EofRec.OldestRecordNumber;
607     }
608 
609     /*
610      * FIXME! During operations the EOF record is the one that is the most
611      * updated (its Oldest & Current record numbers are always up-to
612      * date) while the ones from the header may be unsync. When closing
613      * (or flushing?) the event log, the header's record numbers get
614      * updated with the same values as the ones stored in the EOF record.
615      */
616 
617     /* Verify Start/End offsets boundaries */
618 
619     if ((LogFile->Header.StartOffset >= LogFile->CurrentSize) ||
620         (LogFile->Header.StartOffset & 3) != 0) // StartOffset % sizeof(ULONG) != 0
621     {
622         EVTLTRACE("Invalid start offset 0x%x in `%wZ'.\n",
623                LogFile->Header.StartOffset, &LogFile->FileName);
624         return STATUS_EVENTLOG_FILE_CORRUPT;
625     }
626     if ((LogFile->Header.EndOffset >= LogFile->CurrentSize) ||
627         (LogFile->Header.EndOffset & 3) != 0) // EndOffset % sizeof(ULONG) != 0
628     {
629         EVTLTRACE("Invalid EOF offset 0x%x in `%wZ'.\n",
630                LogFile->Header.EndOffset, &LogFile->FileName);
631         return STATUS_EVENTLOG_FILE_CORRUPT;
632     }
633 
634     if ((LogFile->Header.StartOffset != LogFile->Header.EndOffset) &&
635         (LogFile->Header.MaxSize - LogFile->Header.StartOffset < sizeof(EVENTLOGRECORD)))
636     {
637         /*
638          * If StartOffset does not point to EndOffset i.e. to an EVENTLOGEOF,
639          * it should point to a non-splitted EVENTLOGRECORD.
640          */
641         EVTLTRACE("Invalid start offset 0x%x in `%wZ'.\n",
642                LogFile->Header.StartOffset, &LogFile->FileName);
643         return STATUS_EVENTLOG_FILE_CORRUPT;
644     }
645 
646     if ((LogFile->Header.StartOffset < LogFile->Header.EndOffset) &&
647         (LogFile->Header.EndOffset - LogFile->Header.StartOffset < sizeof(EVENTLOGRECORD)))
648     {
649         /*
650          * In non-wrapping case, there must be enough space between StartOffset
651          * and EndOffset to contain at least a full EVENTLOGRECORD.
652          */
653         EVTLTRACE("Invalid start offset 0x%x or end offset 0x%x in `%wZ'.\n",
654                LogFile->Header.StartOffset, LogFile->Header.EndOffset, &LogFile->FileName);
655         return STATUS_EVENTLOG_FILE_CORRUPT;
656     }
657 
658     if (LogFile->Header.StartOffset <= LogFile->Header.EndOffset)
659     {
660         /*
661          * Non-wrapping case: the (wrapping) free space starting at EndOffset
662          * must be able to contain an EVENTLOGEOF.
663          */
664         if (LogFile->Header.MaxSize - LogFile->Header.EndOffset +
665             LogFile->Header.StartOffset - sizeof(EVENTLOGHEADER) < sizeof(EVENTLOGEOF))
666         {
667             EVTLTRACE("Invalid EOF offset 0x%x in `%wZ'.\n",
668                    LogFile->Header.EndOffset, &LogFile->FileName);
669             return STATUS_EVENTLOG_FILE_CORRUPT;
670         }
671     }
672     else // if (LogFile->Header.StartOffset > LogFile->Header.EndOffset)
673     {
674         /*
675          * Wrapping case: the free space between EndOffset and StartOffset
676          * must be able to contain an EVENTLOGEOF.
677          */
678         if (LogFile->Header.StartOffset - LogFile->Header.EndOffset < sizeof(EVENTLOGEOF))
679         {
680             EVTLTRACE("Invalid EOF offset 0x%x in `%wZ'.\n",
681                    LogFile->Header.EndOffset, &LogFile->FileName);
682             return STATUS_EVENTLOG_FILE_CORRUPT;
683         }
684     }
685 
686     /* Start enumerating the event records from the beginning */
687     RecOffset = LogFile->Header.StartOffset;
688     FileOffset.QuadPart = RecOffset;
689     Wrapping = FALSE;
690 
691     // // FIXME! FIXME!
692     // if (!(LogFile->Header.Flags & ELF_LOGFILE_HEADER_WRAP))
693     // {
694         // DPRINT1("Log file was wrapping but the flag was not set! Fixing...\n");
695         // LogFile->Header.Flags |= ELF_LOGFILE_HEADER_WRAP;
696     // }
697 
698     DPRINT("StartOffset = 0x%x, EndOffset = 0x%x\n",
699            LogFile->Header.StartOffset, LogFile->Header.EndOffset);
700 
701     /*
702      * For non-read-only logs of size < MaxSize, reorganize the events
703      * such that they do not wrap as soon as we write new ones.
704      */
705 #if 0
706     if (!LogFile->ReadOnly)
707     {
708         pRecBuf = LogFile->Allocate(RecBuf.Length, 0, TAG_ELF_BUF);
709         if (pRecBuf == NULL)
710         {
711             DPRINT1("Cannot allocate temporary buffer, skip event reorganization.\n");
712             goto Continue;
713         }
714 
715         // TODO: Do the job!
716     }
717 
718 Continue:
719 
720     DPRINT1("StartOffset = 0x%x, EndOffset = 0x%x\n",
721             LogFile->Header.StartOffset, LogFile->Header.EndOffset);
722 #endif
723 
724     while (FileOffset.QuadPart != LogFile->Header.EndOffset)
725     {
726         if (Wrapping && FileOffset.QuadPart >= RecOffset)
727         {
728             /* We have finished enumerating all the event records */
729             break;
730         }
731 
732         /* Read the next EVENTLOGRECORD header at once (it cannot be split) */
733         Status = LogFile->FileRead(LogFile,
734                                    &FileOffset,
735                                    &RecBuf,
736                                    sizeof(RecBuf),
737                                    &ReadLength);
738         if (!NT_SUCCESS(Status))
739         {
740             EVTLTRACE1("FileRead() failed (Status 0x%08lx)\n", Status);
741             return STATUS_EVENTLOG_FILE_CORRUPT;
742         }
743         if (ReadLength != sizeof(RecBuf))
744         {
745             DPRINT1("Length != sizeof(RecBuf)\n");
746             break;
747         }
748 
749         if (RecBuf.Reserved != LOGFILE_SIGNATURE ||
750             RecBuf.Length < sizeof(EVENTLOGRECORD))
751         {
752             DPRINT1("RecBuf problem\n");
753             break;
754         }
755 
756         /* Allocate a full EVENTLOGRECORD (header + data) */
757         pRecBuf = LogFile->Allocate(RecBuf.Length, 0, TAG_ELF_BUF);
758         if (pRecBuf == NULL)
759         {
760             EVTLTRACE1("Cannot allocate heap!\n");
761             return STATUS_NO_MEMORY;
762         }
763 
764         /* Attempt to read the full EVENTLOGRECORD (can wrap) */
765         Status = ReadLogBuffer(LogFile,
766                                pRecBuf,
767                                RecBuf.Length,
768                                &ReadLength,
769                                &FileOffset,
770                                &NextOffset);
771         if (!NT_SUCCESS(Status))
772         {
773             EVTLTRACE1("ReadLogBuffer failed (Status 0x%08lx)\n", Status);
774             LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
775             return STATUS_EVENTLOG_FILE_CORRUPT;
776         }
777         if (ReadLength != RecBuf.Length)
778         {
779             DPRINT1("Oh oh!!\n");
780             LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
781             break;
782         }
783 
784         // /* If OverWrittenRecords is TRUE and this record has already been read */
785         // if (OverWrittenRecords && (pRecBuf->RecordNumber == LogFile->Header.OldestRecordNumber))
786         // {
787             // LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
788             // break;
789         // }
790 
791         pRecSize2 = (PULONG)((ULONG_PTR)pRecBuf + RecBuf.Length - 4);
792 
793         if (*pRecSize2 != RecBuf.Length)
794         {
795             EVTLTRACE1("Invalid RecordSizeEnd of record %d (0x%x) in `%wZ'\n",
796                     RecordNumber, *pRecSize2, &LogFile->FileName);
797             LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
798             break;
799         }
800 
801         EVTLTRACE("Add new record %d @ offset 0x%x\n", pRecBuf->RecordNumber, FileOffset.QuadPart);
802 
803         RecordNumber++;
804 
805         if (!ElfpAddOffsetInformation(LogFile,
806                                       pRecBuf->RecordNumber,
807                                       FileOffset.QuadPart))
808         {
809             EVTLTRACE1("ElfpAddOffsetInformation() failed!\n");
810             LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
811             return STATUS_EVENTLOG_FILE_CORRUPT;
812         }
813 
814         LogFile->Free(pRecBuf, 0, TAG_ELF_BUF);
815 
816         if (NextOffset.QuadPart == LogFile->Header.EndOffset)
817         {
818             /* We have finished enumerating all the event records */
819             DPRINT("NextOffset.QuadPart == LogFile->Header.EndOffset, break\n");
820             break;
821         }
822 
823         /*
824          * If this was the last event record before the end of the log file,
825          * the next one should start at the beginning of the log and the space
826          * between the last event record and the end of the file is padded.
827          */
828         if (LogFile->Header.MaxSize - NextOffset.QuadPart < sizeof(EVENTLOGRECORD))
829         {
830             /* Wrap to the beginning of the log */
831             DPRINT("Wrap!\n");
832             NextOffset.QuadPart = sizeof(EVENTLOGHEADER);
833         }
834 
835         /*
836          * If the next offset to read is below the current offset,
837          * this means we are wrapping.
838          */
839         if (FileOffset.QuadPart > NextOffset.QuadPart)
840         {
841             DPRINT("Wrapping = TRUE;\n");
842             Wrapping = TRUE;
843         }
844 
845         /* Move the current offset */
846         FileOffset = NextOffset;
847     }
848 
849     /* If the event log was empty, it will now contain one record */
850     if (RecordNumber != 0 && LogFile->Header.OldestRecordNumber == 0)
851         LogFile->Header.OldestRecordNumber = 1;
852 
853     LogFile->Header.CurrentRecordNumber = RecordNumber + LogFile->Header.OldestRecordNumber;
854     if (LogFile->Header.CurrentRecordNumber == 0)
855         LogFile->Header.CurrentRecordNumber = 1;
856 
857     /* Flush the log if it is not read-only */
858     if (!LogFile->ReadOnly)
859     {
860         Status = ElfFlushFile(LogFile);
861         if (!NT_SUCCESS(Status))
862         {
863             EVTLTRACE1("ElfFlushFile() failed (Status 0x%08lx)\n", Status);
864             return STATUS_EVENTLOG_FILE_CORRUPT; // Status;
865         }
866     }
867 
868     return STATUS_SUCCESS;
869 }
870 
871 
872 /* FUNCTIONS *****************************************************************/
873 
874 NTSTATUS
875 NTAPI
876 ElfCreateFile(
877     IN OUT PEVTLOGFILE LogFile,
878     IN PUNICODE_STRING FileName OPTIONAL,
879     IN ULONG    FileSize,
880     IN ULONG    MaxSize,
881     IN ULONG    Retention,
882     IN BOOLEAN  CreateNew,
883     IN BOOLEAN  ReadOnly,
884     IN PELF_ALLOCATE_ROUTINE   Allocate,
885     IN PELF_FREE_ROUTINE       Free,
886     IN PELF_FILE_SET_SIZE_ROUTINE FileSetSize,
887     IN PELF_FILE_WRITE_ROUTINE FileWrite,
888     IN PELF_FILE_READ_ROUTINE  FileRead,
889     IN PELF_FILE_FLUSH_ROUTINE FileFlush) // What about Seek ??
890 {
891     NTSTATUS Status = STATUS_SUCCESS;
892 
893     ASSERT(LogFile);
894 
895     /* Creating a new log file with the 'ReadOnly' flag set is incompatible */
896     if (CreateNew && ReadOnly)
897         return STATUS_INVALID_PARAMETER;
898 
899     RtlZeroMemory(LogFile, sizeof(*LogFile));
900 
901     LogFile->Allocate  = Allocate;
902     LogFile->Free      = Free;
903     LogFile->FileSetSize = FileSetSize;
904     LogFile->FileWrite = FileWrite;
905     LogFile->FileRead  = FileRead;
906     LogFile->FileFlush = FileFlush;
907 
908     /* Copy the log file name if provided (optional) */
909     RtlInitEmptyUnicodeString(&LogFile->FileName, NULL, 0);
910     if (FileName && FileName->Buffer && FileName->Length &&
911         (FileName->Length <= FileName->MaximumLength))
912     {
913         LogFile->FileName.Buffer = LogFile->Allocate(FileName->Length,
914                                                      HEAP_ZERO_MEMORY,
915                                                      TAG_ELF);
916         if (LogFile->FileName.Buffer)
917         {
918             LogFile->FileName.MaximumLength = FileName->Length;
919             RtlCopyUnicodeString(&LogFile->FileName, FileName);
920         }
921     }
922 
923     LogFile->OffsetInfo = LogFile->Allocate(OFFSET_INFO_INCREMENT * sizeof(EVENT_OFFSET_INFO),
924                                             HEAP_ZERO_MEMORY,
925                                             TAG_ELF);
926     if (LogFile->OffsetInfo == NULL)
927     {
928         EVTLTRACE1("Cannot allocate heap\n");
929         Status = STATUS_NO_MEMORY;
930         goto Quit;
931     }
932     LogFile->OffsetInfoSize = OFFSET_INFO_INCREMENT;
933     LogFile->OffsetInfoNext = 0;
934 
935     // FIXME: Always use the regitry values for MaxSize,
936     // even for existing logs!
937 
938     // FIXME: On Windows, EventLog uses the MaxSize setting
939     // from the registry itself; the MaxSize from the header
940     // is just for information purposes.
941 
942     EVTLTRACE("Initializing log file `%wZ'\n", &LogFile->FileName);
943 
944     LogFile->ReadOnly = ReadOnly; // !CreateNew && ReadOnly;
945 
946     if (CreateNew)
947         Status = ElfpInitNewFile(LogFile, FileSize, MaxSize, Retention);
948     else
949         Status = ElfpInitExistingFile(LogFile, FileSize, /* MaxSize, */ Retention);
950 
951 Quit:
952     if (!NT_SUCCESS(Status))
953     {
954         if (LogFile->OffsetInfo)
955             LogFile->Free(LogFile->OffsetInfo, 0, TAG_ELF);
956 
957         if (LogFile->FileName.Buffer)
958             LogFile->Free(LogFile->FileName.Buffer, 0, TAG_ELF);
959     }
960 
961     return Status;
962 }
963 
964 NTSTATUS
965 NTAPI
966 ElfReCreateFile(
967     IN PEVTLOGFILE LogFile)
968 {
969     ASSERT(LogFile);
970 
971     return ElfpInitNewFile(LogFile,
972                            LogFile->CurrentSize,
973                            LogFile->Header.MaxSize,
974                            LogFile->Header.Retention);
975 }
976 
977 NTSTATUS
978 NTAPI
979 ElfBackupFile(
980     IN PEVTLOGFILE LogFile,
981     IN PEVTLOGFILE BackupLogFile)
982 {
983     NTSTATUS Status;
984 
985     LARGE_INTEGER FileOffset;
986     SIZE_T ReadLength, WrittenLength;
987     PEVENTLOGHEADER Header;
988     EVENTLOGRECORD RecBuf;
989     EVENTLOGEOF EofRec;
990     ULONG i;
991     ULONG RecOffset;
992     PVOID Buffer = NULL;
993 
994     ASSERT(LogFile);
995 
996     RtlZeroMemory(BackupLogFile, sizeof(*BackupLogFile));
997 
998     BackupLogFile->FileSetSize = LogFile->FileSetSize;
999     BackupLogFile->FileWrite = LogFile->FileWrite;
1000     BackupLogFile->FileFlush = LogFile->FileFlush;
1001 
1002     // BackupLogFile->CurrentSize = LogFile->CurrentSize;
1003 
1004     BackupLogFile->ReadOnly = FALSE;
1005 
1006     /* Initialize the (dirty) log file header */
1007     Header = &BackupLogFile->Header;
1008     Header->HeaderSize = sizeof(EVENTLOGHEADER);
1009     Header->Signature = LOGFILE_SIGNATURE;
1010     Header->MajorVersion = MAJORVER;
1011     Header->MinorVersion = MINORVER;
1012     Header->StartOffset = sizeof(EVENTLOGHEADER);
1013     Header->EndOffset = sizeof(EVENTLOGHEADER);
1014     Header->CurrentRecordNumber = 1;
1015     Header->OldestRecordNumber = 0;
1016     Header->MaxSize = LogFile->Header.MaxSize;
1017     Header->Flags = ELF_LOGFILE_HEADER_DIRTY;
1018     Header->Retention = LogFile->Header.Retention;
1019     Header->EndHeaderSize = sizeof(EVENTLOGHEADER);
1020 
1021     /* Write the (dirty) log file header */
1022     FileOffset.QuadPart = 0LL;
1023     Status = BackupLogFile->FileWrite(BackupLogFile,
1024                                       &FileOffset,
1025                                       Header,
1026                                       sizeof(EVENTLOGHEADER),
1027                                       &WrittenLength);
1028     if (!NT_SUCCESS(Status))
1029     {
1030         EVTLTRACE1("Failed to write the log file header (Status 0x%08lx)\n", Status);
1031         goto Quit;
1032     }
1033 
1034     for (i = LogFile->Header.OldestRecordNumber; i < LogFile->Header.CurrentRecordNumber; i++)
1035     {
1036         RecOffset = ElfpOffsetByNumber(LogFile, i);
1037         if (RecOffset == 0)
1038             break;
1039 
1040         /* Read the next EVENTLOGRECORD header at once (it cannot be split) */
1041         FileOffset.QuadPart = RecOffset;
1042         Status = LogFile->FileRead(LogFile,
1043                                    &FileOffset,
1044                                    &RecBuf,
1045                                    sizeof(RecBuf),
1046                                    &ReadLength);
1047         if (!NT_SUCCESS(Status))
1048         {
1049             EVTLTRACE1("FileRead() failed (Status 0x%08lx)\n", Status);
1050             goto Quit;
1051         }
1052 
1053         // if (ReadLength != sizeof(RecBuf))
1054             // break;
1055 
1056         Buffer = LogFile->Allocate(RecBuf.Length, 0, TAG_ELF_BUF);
1057         if (Buffer == NULL)
1058         {
1059             EVTLTRACE1("Allocate() failed!\n");
1060             goto Quit;
1061         }
1062 
1063         /* Read the full EVENTLOGRECORD (header + data) with wrapping */
1064         Status = ReadLogBuffer(LogFile,
1065                                Buffer,
1066                                RecBuf.Length,
1067                                &ReadLength,
1068                                &FileOffset,
1069                                NULL);
1070         if (!NT_SUCCESS(Status))
1071         {
1072             EVTLTRACE1("ReadLogBuffer failed (Status 0x%08lx)\n", Status);
1073             LogFile->Free(Buffer, 0, TAG_ELF_BUF);
1074             // Status = STATUS_EVENTLOG_FILE_CORRUPT;
1075             goto Quit;
1076         }
1077 
1078         /* Write the event record (no wrap for the backup log) */
1079         Status = BackupLogFile->FileWrite(BackupLogFile,
1080                                           NULL,
1081                                           Buffer,
1082                                           RecBuf.Length,
1083                                           &WrittenLength);
1084         if (!NT_SUCCESS(Status))
1085         {
1086             EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
1087             LogFile->Free(Buffer, 0, TAG_ELF_BUF);
1088             goto Quit;
1089         }
1090 
1091         /* Update the header information */
1092         Header->EndOffset += RecBuf.Length;
1093 
1094         /* Free the buffer */
1095         LogFile->Free(Buffer, 0, TAG_ELF_BUF);
1096         Buffer = NULL;
1097     }
1098 
1099 // Quit:
1100 
1101     /* Initialize the ELF_EOF_RECORD and write it (no wrap for the backup log) */
1102     RtlCopyMemory(&EofRec, &EOFRecord, sizeof(EOFRecord));
1103     EofRec.BeginRecord = Header->StartOffset;
1104     EofRec.EndRecord   = Header->EndOffset;
1105     EofRec.CurrentRecordNumber = LogFile->Header.CurrentRecordNumber;
1106     EofRec.OldestRecordNumber  = LogFile->Header.OldestRecordNumber;
1107 
1108     Status = BackupLogFile->FileWrite(BackupLogFile,
1109                                       NULL,
1110                                       &EofRec,
1111                                       sizeof(EofRec),
1112                                       &WrittenLength);
1113     if (!NT_SUCCESS(Status))
1114     {
1115         EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
1116         goto Quit;
1117     }
1118 
1119     /* Update the header information */
1120     Header->CurrentRecordNumber = LogFile->Header.CurrentRecordNumber;
1121     Header->OldestRecordNumber  = LogFile->Header.OldestRecordNumber;
1122     Header->MaxSize = ROUND_UP(Header->EndOffset + sizeof(EofRec), sizeof(ULONG));
1123     Header->Flags = 0; // FIXME?
1124 
1125     /* Flush the log file - Write the (clean) log file header */
1126     Status = ElfFlushFile(BackupLogFile);
1127 
1128 Quit:
1129     return Status;
1130 }
1131 
1132 NTSTATUS
1133 NTAPI
1134 ElfFlushFile(
1135     IN PEVTLOGFILE LogFile)
1136 {
1137     NTSTATUS Status;
1138     LARGE_INTEGER FileOffset;
1139     SIZE_T WrittenLength;
1140 
1141     ASSERT(LogFile);
1142 
1143     if (LogFile->ReadOnly)
1144         return STATUS_SUCCESS; // STATUS_ACCESS_DENIED;
1145 
1146     /*
1147      * NOTE that both the EOF record *AND* the log file header
1148      * are supposed to be already updated!
1149      * We just remove the dirty log bit.
1150      */
1151     LogFile->Header.Flags &= ~ELF_LOGFILE_HEADER_DIRTY;
1152 
1153     /* Update the log file header */
1154     FileOffset.QuadPart = 0LL;
1155     Status = LogFile->FileWrite(LogFile,
1156                                 &FileOffset,
1157                                 &LogFile->Header,
1158                                 sizeof(EVENTLOGHEADER),
1159                                 &WrittenLength);
1160     if (!NT_SUCCESS(Status))
1161     {
1162         EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
1163         return Status;
1164     }
1165 
1166     /* Flush the log file */
1167     Status = LogFile->FileFlush(LogFile, NULL, 0);
1168     if (!NT_SUCCESS(Status))
1169     {
1170         EVTLTRACE1("FileFlush() failed (Status 0x%08lx)\n", Status);
1171         return Status;
1172     }
1173 
1174     return STATUS_SUCCESS;
1175 }
1176 
1177 VOID
1178 NTAPI
1179 ElfCloseFile(  // ElfFree
1180     IN PEVTLOGFILE LogFile)
1181 {
1182     ASSERT(LogFile);
1183 
1184     /* Flush the log file */
1185     ElfFlushFile(LogFile);
1186 
1187     /* Free the data */
1188     LogFile->Free(LogFile->OffsetInfo, 0, TAG_ELF);
1189 
1190     if (LogFile->FileName.Buffer)
1191         LogFile->Free(LogFile->FileName.Buffer, 0, TAG_ELF);
1192     RtlInitEmptyUnicodeString(&LogFile->FileName, NULL, 0);
1193 }
1194 
1195 NTSTATUS
1196 NTAPI
1197 ElfReadRecord(
1198     IN  PEVTLOGFILE LogFile,
1199     IN  ULONG RecordNumber,
1200     OUT PEVENTLOGRECORD Record,
1201     IN  SIZE_T  BufSize, // Length
1202     OUT PSIZE_T BytesRead OPTIONAL,
1203     OUT PSIZE_T BytesNeeded OPTIONAL)
1204 {
1205     NTSTATUS Status;
1206     LARGE_INTEGER FileOffset;
1207     ULONG RecOffset;
1208     SIZE_T RecSize;
1209     SIZE_T ReadLength;
1210 
1211     ASSERT(LogFile);
1212 
1213     if (BytesRead)
1214         *BytesRead = 0;
1215 
1216     if (BytesNeeded)
1217         *BytesNeeded = 0;
1218 
1219     /* Retrieve the offset of the event record */
1220     RecOffset = ElfpOffsetByNumber(LogFile, RecordNumber);
1221     if (RecOffset == 0)
1222         return STATUS_NOT_FOUND;
1223 
1224     /* Retrieve its full size */
1225     FileOffset.QuadPart = RecOffset;
1226     Status = LogFile->FileRead(LogFile,
1227                                &FileOffset,
1228                                &RecSize,
1229                                sizeof(RecSize),
1230                                &ReadLength);
1231     if (!NT_SUCCESS(Status))
1232     {
1233         EVTLTRACE1("FileRead() failed (Status 0x%08lx)\n", Status);
1234         // Status = STATUS_EVENTLOG_FILE_CORRUPT;
1235         return Status;
1236     }
1237 
1238     /* Check whether the buffer is big enough to hold the event record */
1239     if (BufSize < RecSize)
1240     {
1241         if (BytesNeeded)
1242             *BytesNeeded = RecSize;
1243 
1244         return STATUS_BUFFER_TOO_SMALL;
1245     }
1246 
1247     /* Read the event record into the buffer */
1248     FileOffset.QuadPart = RecOffset;
1249     Status = ReadLogBuffer(LogFile,
1250                            Record,
1251                            RecSize,
1252                            &ReadLength,
1253                            &FileOffset,
1254                            NULL);
1255     if (!NT_SUCCESS(Status))
1256     {
1257         EVTLTRACE1("ReadLogBuffer failed (Status 0x%08lx)\n", Status);
1258         // Status = STATUS_EVENTLOG_FILE_CORRUPT;
1259     }
1260 
1261     if (BytesRead)
1262         *BytesRead = ReadLength;
1263 
1264     return Status;
1265 }
1266 
1267 NTSTATUS
1268 NTAPI
1269 ElfWriteRecord(
1270     IN PEVTLOGFILE LogFile,
1271     IN PEVENTLOGRECORD Record,
1272     IN SIZE_T BufSize)
1273 {
1274     NTSTATUS Status;
1275     LARGE_INTEGER FileOffset, NextOffset;
1276     SIZE_T ReadLength, WrittenLength;
1277     EVENTLOGEOF EofRec;
1278     EVENTLOGRECORD RecBuf;
1279     ULONG FreeSpace = 0;
1280     ULONG UpperBound;
1281     ULONG RecOffset, WriteOffset;
1282 
1283     ASSERT(LogFile);
1284 
1285     if (LogFile->ReadOnly)
1286         return STATUS_ACCESS_DENIED;
1287 
1288     // ASSERT(sizeof(*Record) == sizeof(RecBuf));
1289 
1290     if (!Record || BufSize < sizeof(*Record))
1291         return STATUS_INVALID_PARAMETER;
1292 
1293     Record->RecordNumber = LogFile->Header.CurrentRecordNumber;
1294 
1295     /* Compute the available log free space */
1296     if (LogFile->Header.StartOffset <= LogFile->Header.EndOffset)
1297         FreeSpace = LogFile->Header.MaxSize - LogFile->Header.EndOffset + LogFile->Header.StartOffset - sizeof(EVENTLOGHEADER);
1298     else // if (LogFile->Header.StartOffset > LogFile->Header.EndOffset)
1299         FreeSpace = LogFile->Header.StartOffset - LogFile->Header.EndOffset;
1300 
1301     LogFile->Header.Flags |= ELF_LOGFILE_HEADER_DIRTY;
1302 
1303     /* If the event log was empty, it will now contain one record */
1304     if (LogFile->Header.OldestRecordNumber == 0)
1305         LogFile->Header.OldestRecordNumber = 1;
1306 
1307     /* By default we append the new record at the old EOF record offset */
1308     WriteOffset = LogFile->Header.EndOffset;
1309 
1310     /*
1311      * Check whether the log is going to wrap (the events being overwritten).
1312      */
1313 
1314     if (LogFile->Header.StartOffset <= LogFile->Header.EndOffset)
1315         UpperBound = LogFile->Header.MaxSize;
1316     else // if (LogFile->Header.StartOffset > LogFile->Header.EndOffset)
1317         UpperBound = LogFile->Header.StartOffset;
1318 
1319     // if (LogFile->Header.MaxSize - WriteOffset < BufSize + sizeof(EofRec))
1320     if (UpperBound - WriteOffset < BufSize + sizeof(EofRec))
1321     {
1322         EVTLTRACE("The event log file has reached maximum size (0x%x), wrapping...\n"
1323                "UpperBound = 0x%x, WriteOffset = 0x%x, BufSize = 0x%x\n",
1324                LogFile->Header.MaxSize, UpperBound, WriteOffset, BufSize);
1325         /* This will be done later */
1326     }
1327 
1328     if ( (LogFile->Header.StartOffset < LogFile->Header.EndOffset) &&
1329          (LogFile->Header.MaxSize - WriteOffset < sizeof(RecBuf)) ) // (UpperBound - WriteOffset < sizeof(RecBuf))
1330     {
1331         // ASSERT(UpperBound  == LogFile->Header.MaxSize);
1332         // ASSERT(WriteOffset == LogFile->Header.EndOffset);
1333 
1334         /*
1335          * We cannot fit the EVENTLOGRECORD header of the buffer before
1336          * the end of the file. We need to pad the end of the log with
1337          * 0x00000027, normally we will need to pad at most 0x37 bytes
1338          * (corresponding to sizeof(EVENTLOGRECORD) - 1).
1339          */
1340 
1341         /* Rewind to the beginning of the log, just after the header */
1342         WriteOffset = sizeof(EVENTLOGHEADER);
1343         /**/UpperBound = LogFile->Header.StartOffset;/**/
1344 
1345         FreeSpace = LogFile->Header.StartOffset - WriteOffset;
1346 
1347         LogFile->Header.Flags |= ELF_LOGFILE_HEADER_WRAP;
1348     }
1349     /*
1350      * Otherwise, we can fit the header and only part
1351      * of the data will overwrite the oldest records.
1352      *
1353      * It might be possible that all the event record can fit in one piece,
1354      * but that the EOF record needs to be split. This is not a problem,
1355      * EVENTLOGEOF can be splitted while EVENTLOGRECORD cannot be.
1356      */
1357 
1358     if (UpperBound - WriteOffset < BufSize + sizeof(EofRec))
1359     {
1360         ULONG OrgOldestRecordNumber, OldestRecordNumber;
1361 
1362         // DPRINT("EventLogFile has reached maximum size, wrapping...\n");
1363 
1364         OldestRecordNumber = OrgOldestRecordNumber = LogFile->Header.OldestRecordNumber;
1365 
1366         // FIXME: Assert whether LogFile->Header.StartOffset is the beginning of a record???
1367         // NOTE: It should be, by construction (and this should have been checked when
1368         // initializing a new, or existing log).
1369 
1370         /*
1371          * Determine how many old records need to be overwritten.
1372          * Check the size of the record as the record added may be larger.
1373          * Need to take into account that we append the EOF record.
1374          */
1375         while (FreeSpace < BufSize + sizeof(EofRec))
1376         {
1377             /* Get the oldest record data */
1378             RecOffset = ElfpOffsetByNumber(LogFile, OldestRecordNumber);
1379             if (RecOffset == 0)
1380             {
1381                 EVTLTRACE1("Record number %d cannot be found, or log file is full and cannot wrap!\n", OldestRecordNumber);
1382                 LogFile->Header.Flags |= ELF_LOGFILE_LOGFULL_WRITTEN;
1383                 return STATUS_LOG_FILE_FULL;
1384             }
1385 
1386             RtlZeroMemory(&RecBuf, sizeof(RecBuf));
1387 
1388             FileOffset.QuadPart = RecOffset;
1389             Status = LogFile->FileRead(LogFile,
1390                                        &FileOffset,
1391                                        &RecBuf,
1392                                        sizeof(RecBuf),
1393                                        &ReadLength);
1394             if (!NT_SUCCESS(Status))
1395             {
1396                 EVTLTRACE1("FileRead() failed (Status 0x%08lx)\n", Status);
1397                 // Status = STATUS_EVENTLOG_FILE_CORRUPT;
1398                 return Status;
1399             }
1400 
1401             if (RecBuf.Reserved != LOGFILE_SIGNATURE)
1402             {
1403                 EVTLTRACE1("The event log file is corrupted!\n");
1404                 return STATUS_EVENTLOG_FILE_CORRUPT;
1405             }
1406 
1407             /*
1408              * Check whether this event can be overwritten by comparing its
1409              * written timestamp with the log's retention value. This value
1410              * is the time interval, in seconds, that events records are
1411              * protected from being overwritten.
1412              *
1413              * If the retention value is zero the events are always overwritten.
1414              *
1415              * If the retention value is non-zero, when the age of an event,
1416              * in seconds, reaches or exceeds this value, it can be overwritten.
1417              * Also if the events are in the future, we do not overwrite them.
1418              */
1419             if (LogFile->Header.Retention != 0 &&
1420                 (Record->TimeWritten <  RecBuf.TimeWritten ||
1421                 (Record->TimeWritten >= RecBuf.TimeWritten &&
1422                  Record->TimeWritten -  RecBuf.TimeWritten < LogFile->Header.Retention)))
1423             {
1424                 EVTLTRACE1("The event log file is full and cannot wrap because of the retention policy.\n");
1425                 LogFile->Header.Flags |= ELF_LOGFILE_LOGFULL_WRITTEN;
1426                 return STATUS_LOG_FILE_FULL;
1427             }
1428 
1429             /*
1430              * Advance the oldest record number, add the event record length
1431              * (as long as it is valid...) then take account for the possible
1432              * paddind after the record, in case this is the last one at the
1433              * end of the file.
1434              */
1435             OldestRecordNumber++;
1436             RecOffset += RecBuf.Length;
1437             FreeSpace += RecBuf.Length;
1438 
1439             /*
1440              * If this was the last event record before the end of the log file,
1441              * the next one should start at the beginning of the log and the space
1442              * between the last event record and the end of the file is padded.
1443              */
1444             if (LogFile->Header.MaxSize - RecOffset < sizeof(EVENTLOGRECORD))
1445             {
1446                 /* Add the padding size */
1447                 FreeSpace += LogFile->Header.MaxSize - RecOffset;
1448             }
1449         }
1450 
1451         EVTLTRACE("Record will fit. FreeSpace %d, BufSize %d\n", FreeSpace, BufSize);
1452 
1453         /* The log records are wrapping */
1454         LogFile->Header.Flags |= ELF_LOGFILE_HEADER_WRAP;
1455 
1456 
1457         // FIXME: May lead to corruption if the other subsequent calls fail...
1458 
1459         /*
1460          * We have validated all the region of events to be discarded,
1461          * now we can perform their deletion.
1462          */
1463         ElfpDeleteOffsetInformation(LogFile, OrgOldestRecordNumber, OldestRecordNumber - 1);
1464         LogFile->Header.OldestRecordNumber = OldestRecordNumber;
1465         LogFile->Header.StartOffset = ElfpOffsetByNumber(LogFile, OldestRecordNumber);
1466         if (LogFile->Header.StartOffset == 0)
1467         {
1468             /*
1469              * We have deleted all the existing event records to make place
1470              * for the new one. We can put it at the start of the event log.
1471              */
1472             LogFile->Header.StartOffset = sizeof(EVENTLOGHEADER);
1473             WriteOffset = LogFile->Header.StartOffset;
1474             LogFile->Header.EndOffset = WriteOffset;
1475         }
1476 
1477         EVTLTRACE("MaxSize = 0x%x, StartOffset = 0x%x, WriteOffset = 0x%x, EndOffset = 0x%x, BufSize = 0x%x\n"
1478                   "OldestRecordNumber = %d\n",
1479                   LogFile->Header.MaxSize, LogFile->Header.StartOffset, WriteOffset, LogFile->Header.EndOffset, BufSize,
1480                   OldestRecordNumber);
1481     }
1482 
1483     /*
1484      * Expand the log file if needed.
1485      * NOTE: It may be needed to perform this task a bit sooner if we need
1486      * such a thing for performing read operations, in the future...
1487      * Or if this operation needs to modify 'FreeSpace'...
1488      */
1489     if (LogFile->CurrentSize < LogFile->Header.MaxSize)
1490     {
1491         EVTLTRACE1("Expanding the log file from %lu to %lu\n",
1492                 LogFile->CurrentSize, LogFile->Header.MaxSize);
1493 
1494         LogFile->CurrentSize = LogFile->Header.MaxSize;
1495         LogFile->FileSetSize(LogFile, LogFile->CurrentSize, 0);
1496     }
1497 
1498     /* Since we can write events in the log, clear the log full flag */
1499     LogFile->Header.Flags &= ~ELF_LOGFILE_LOGFULL_WRITTEN;
1500 
1501     /* Pad the end of the log */
1502     // if (LogFile->Header.EndOffset + sizeof(RecBuf) > LogFile->Header.MaxSize)
1503     if (WriteOffset < LogFile->Header.EndOffset)
1504     {
1505         /* Pad all the space from LogFile->Header.EndOffset to LogFile->Header.MaxSize */
1506         WrittenLength = ROUND_DOWN(LogFile->Header.MaxSize - LogFile->Header.EndOffset, sizeof(ULONG));
1507         RtlFillMemoryUlong(&RecBuf, WrittenLength, 0x00000027);
1508 
1509         FileOffset.QuadPart = LogFile->Header.EndOffset;
1510         Status = LogFile->FileWrite(LogFile,
1511                                     &FileOffset,
1512                                     &RecBuf,
1513                                     WrittenLength,
1514                                     &WrittenLength);
1515         if (!NT_SUCCESS(Status))
1516         {
1517             EVTLTRACE1("FileWrite() failed (Status 0x%08lx)\n", Status);
1518             // return Status;
1519         }
1520     }
1521 
1522     /* Write the event record buffer with possible wrap at offset sizeof(EVENTLOGHEADER) */
1523     FileOffset.QuadPart = WriteOffset;
1524     Status = WriteLogBuffer(LogFile,
1525                             Record,
1526                             BufSize,
1527                             &WrittenLength,
1528                             &FileOffset,
1529                             &NextOffset);
1530     if (!NT_SUCCESS(Status))
1531     {
1532         EVTLTRACE1("WriteLogBuffer failed (Status 0x%08lx)\n", Status);
1533         return Status;
1534     }
1535     /* FileOffset now contains the offset just after the end of the record buffer */
1536     FileOffset = NextOffset;
1537 
1538     if (!ElfpAddOffsetInformation(LogFile,
1539                                   Record->RecordNumber,
1540                                   WriteOffset))
1541     {
1542         return STATUS_NO_MEMORY; // STATUS_EVENTLOG_FILE_CORRUPT;
1543     }
1544 
1545     LogFile->Header.CurrentRecordNumber++;
1546     if (LogFile->Header.CurrentRecordNumber == 0)
1547         LogFile->Header.CurrentRecordNumber = 1;
1548 
1549     /*
1550      * Write the new EOF record offset just after the event record.
1551      * The EOF record can wrap (be splitted) if less than sizeof(EVENTLOGEOF)
1552      * bytes remains between the end of the record and the end of the log file.
1553      */
1554     LogFile->Header.EndOffset = FileOffset.QuadPart;
1555 
1556     RtlCopyMemory(&EofRec, &EOFRecord, sizeof(EOFRecord));
1557     EofRec.BeginRecord = LogFile->Header.StartOffset;
1558     EofRec.EndRecord   = LogFile->Header.EndOffset;
1559     EofRec.CurrentRecordNumber = LogFile->Header.CurrentRecordNumber;
1560     EofRec.OldestRecordNumber  = LogFile->Header.OldestRecordNumber;
1561 
1562     // FileOffset.QuadPart = LogFile->Header.EndOffset;
1563     Status = WriteLogBuffer(LogFile,
1564                             &EofRec,
1565                             sizeof(EofRec),
1566                             &WrittenLength,
1567                             &FileOffset,
1568                             &NextOffset);
1569     if (!NT_SUCCESS(Status))
1570     {
1571         EVTLTRACE1("WriteLogBuffer failed (Status 0x%08lx)\n", Status);
1572         return Status;
1573     }
1574     FileOffset = NextOffset;
1575 
1576     /* Flush the log file */
1577     Status = ElfFlushFile(LogFile);
1578     if (!NT_SUCCESS(Status))
1579     {
1580         EVTLTRACE1("ElfFlushFile() failed (Status 0x%08lx)\n", Status);
1581         return STATUS_EVENTLOG_FILE_CORRUPT; // Status;
1582     }
1583 
1584     return Status;
1585 }
1586 
1587 ULONG
1588 NTAPI
1589 ElfGetOldestRecord(
1590     IN PEVTLOGFILE LogFile)
1591 {
1592     ASSERT(LogFile);
1593     return LogFile->Header.OldestRecordNumber;
1594 }
1595 
1596 ULONG
1597 NTAPI
1598 ElfGetCurrentRecord(
1599     IN PEVTLOGFILE LogFile)
1600 {
1601     ASSERT(LogFile);
1602     return LogFile->Header.CurrentRecordNumber;
1603 }
1604 
1605 ULONG
1606 NTAPI
1607 ElfGetFlags(
1608     IN PEVTLOGFILE LogFile)
1609 {
1610     ASSERT(LogFile);
1611     return LogFile->Header.Flags;
1612 }
1613 
1614 #if DBG
1615 VOID PRINT_HEADER(PEVENTLOGHEADER Header)
1616 {
1617     ULONG Flags = Header->Flags;
1618 
1619     EVTLTRACE1("PRINT_HEADER(0x%p)\n", Header);
1620 
1621     DbgPrint("HeaderSize    = %lu\n" , Header->HeaderSize);
1622     DbgPrint("Signature     = 0x%x\n", Header->Signature);
1623     DbgPrint("MajorVersion  = %lu\n" , Header->MajorVersion);
1624     DbgPrint("MinorVersion  = %lu\n" , Header->MinorVersion);
1625     DbgPrint("StartOffset   = 0x%x\n", Header->StartOffset);
1626     DbgPrint("EndOffset     = 0x%x\n", Header->EndOffset);
1627     DbgPrint("CurrentRecordNumber = %lu\n", Header->CurrentRecordNumber);
1628     DbgPrint("OldestRecordNumber  = %lu\n", Header->OldestRecordNumber);
1629     DbgPrint("MaxSize       = 0x%x\n", Header->MaxSize);
1630     DbgPrint("Retention     = 0x%x\n", Header->Retention);
1631     DbgPrint("EndHeaderSize = %lu\n" , Header->EndHeaderSize);
1632     DbgPrint("Flags: ");
1633     if (Flags & ELF_LOGFILE_HEADER_DIRTY)
1634     {
1635         DbgPrint("ELF_LOGFILE_HEADER_DIRTY");
1636         Flags &= ~ELF_LOGFILE_HEADER_DIRTY;
1637     }
1638     if (Flags) DbgPrint(" | ");
1639     if (Flags & ELF_LOGFILE_HEADER_WRAP)
1640     {
1641         DbgPrint("ELF_LOGFILE_HEADER_WRAP");
1642         Flags &= ~ELF_LOGFILE_HEADER_WRAP;
1643     }
1644     if (Flags) DbgPrint(" | ");
1645     if (Flags & ELF_LOGFILE_LOGFULL_WRITTEN)
1646     {
1647         DbgPrint("ELF_LOGFILE_LOGFULL_WRITTEN");
1648         Flags &= ~ELF_LOGFILE_LOGFULL_WRITTEN;
1649     }
1650     if (Flags) DbgPrint(" | ");
1651     if (Flags & ELF_LOGFILE_ARCHIVE_SET)
1652     {
1653         DbgPrint("ELF_LOGFILE_ARCHIVE_SET");
1654         Flags &= ~ELF_LOGFILE_ARCHIVE_SET;
1655     }
1656     if (Flags) DbgPrint(" | 0x%x", Flags);
1657     DbgPrint("\n");
1658 }
1659 #endif
1660