golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  using System;
     7  using System.IO;
     8  using System.IO.MemoryMappedFiles;
     9  using System.Text;
    10  using System.Collections.Generic;
    11  using System.Threading;
    12  using System.Runtime.CompilerServices;
    13  
    14  namespace Tunnel
    15  {
    16      public class Ringlogger
    17      {
    18          private struct UnixTimestamp
    19          {
    20              private Int64 _ns;
    21              public UnixTimestamp(Int64 ns) => _ns = ns;
    22              public bool IsEmpty => _ns == 0;
    23              public static UnixTimestamp Empty => new UnixTimestamp(0);
    24              public static UnixTimestamp Now
    25              {
    26                  get
    27                  {
    28                      var now = DateTimeOffset.UtcNow;
    29                      var ns = (now.Subtract(DateTimeOffset.FromUnixTimeSeconds(0)).Ticks * 100) % 1000000000;
    30                      return new UnixTimestamp(now.ToUnixTimeSeconds() * 1000000000 + ns);
    31                  }
    32              }
    33              public Int64 Nanoseconds => _ns;
    34              public override string ToString()
    35              {
    36                  return DateTimeOffset.FromUnixTimeSeconds(_ns / 1000000000).LocalDateTime.ToString("yyyy'-'MM'-'dd HH':'mm':'ss'.'") + ((_ns % 1000000000).ToString() + "00000").Substring(0, 6);
    37              }
    38          }
    39          private struct Line
    40          {
    41              private const int maxLineLength = 512;
    42              private const int offsetTimeNs = 0;
    43              private const int offsetLine = 8;
    44  
    45              private readonly MemoryMappedViewAccessor _view;
    46              private readonly int _start;
    47              public Line(MemoryMappedViewAccessor view, UInt32 index) => (_view, _start) = (view, (int)(Log.HeaderBytes + index * Bytes));
    48  
    49              public static int Bytes => maxLineLength + offsetLine;
    50  
    51              public UnixTimestamp Timestamp
    52              {
    53                  get => new UnixTimestamp(_view.ReadInt64(_start + offsetTimeNs));
    54                  set => _view.Write(_start + offsetTimeNs, value.Nanoseconds);
    55              }
    56  
    57              public string Text
    58              {
    59                  get
    60                  {
    61                      var textBytes = new byte[maxLineLength];
    62                      _view.ReadArray(_start + offsetLine, textBytes, 0, textBytes.Length);
    63                      var nullByte = Array.IndexOf<byte>(textBytes, 0);
    64                      if (nullByte <= 0)
    65                          return null;
    66                      return Encoding.UTF8.GetString(textBytes, 0, nullByte);
    67                  }
    68                  set
    69                  {
    70                      if (value == null)
    71                      {
    72                          _view.WriteArray(_start + offsetLine, new byte[maxLineLength], 0, maxLineLength);
    73                          return;
    74                      }
    75                      var textBytes = Encoding.UTF8.GetBytes(value);
    76                      var bytesToWrite = Math.Min(maxLineLength - 1, textBytes.Length);
    77                      _view.Write(_start + offsetLine + bytesToWrite, (byte)0);
    78                      _view.WriteArray(_start + offsetLine, textBytes, 0, bytesToWrite);
    79                  }
    80              }
    81  
    82              public override string ToString()
    83              {
    84                  var time = Timestamp;
    85                  if (time.IsEmpty)
    86                      return null;
    87                  var text = Text;
    88                  if (text == null)
    89                      return null;
    90                  return string.Format("{0}: {1}", time, text);
    91              }
    92          }
    93          private struct Log
    94          {
    95              private const UInt32 maxLines = 2048;
    96              private const UInt32 magic = 0xbadbabe;
    97              private const int offsetMagic = 0;
    98              private const int offsetNextIndex = 4;
    99              private const int offsetLines = 8;
   100  
   101              private readonly MemoryMappedViewAccessor _view;
   102              public Log(MemoryMappedViewAccessor view) => _view = view;
   103  
   104              public static int HeaderBytes => offsetLines;
   105              public static int Bytes => (int)(HeaderBytes + Line.Bytes * maxLines);
   106  
   107              public UInt32 ExpectedMagic => magic;
   108              public UInt32 Magic
   109              {
   110                  get => _view.ReadUInt32(offsetMagic);
   111                  set => _view.Write(offsetMagic, value);
   112              }
   113  
   114              public UInt32 NextIndex
   115              {
   116                  get => _view.ReadUInt32(offsetNextIndex);
   117                  set => _view.Write(offsetNextIndex, value);
   118              }
   119              public unsafe UInt32 InsertNextIndex()
   120              {
   121                  byte* pointer = null;
   122                  _view.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
   123                  var ret = (UInt32)Interlocked.Increment(ref Unsafe.AsRef<Int32>(pointer + offsetNextIndex));
   124                  _view.SafeMemoryMappedViewHandle.ReleasePointer();
   125                  return ret;
   126              }
   127  
   128              public UInt32 LineCount => maxLines;
   129              public Line this[UInt32 i] => new Line(_view, i % maxLines);
   130  
   131              public void Clear() => _view.WriteArray(0, new byte[Bytes], 0, Bytes);
   132          }
   133  
   134          private readonly Log _log;
   135          private readonly string _tag;
   136  
   137          public Ringlogger(string filename, string tag)
   138          {
   139              var file = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
   140              file.SetLength(Log.Bytes);
   141              var mmap = MemoryMappedFile.CreateFromFile(file, null, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false);
   142              var view = mmap.CreateViewAccessor(0, Log.Bytes, MemoryMappedFileAccess.ReadWrite);
   143              _log = new Log(view);
   144              if (_log.Magic != _log.ExpectedMagic)
   145              {
   146                  _log.Clear();
   147                  _log.Magic = _log.ExpectedMagic;
   148              }
   149              _tag = tag;
   150          }
   151  
   152          public void Write(string line)
   153          {
   154              var time = UnixTimestamp.Now;
   155              var entry = _log[_log.InsertNextIndex() - 1];
   156              entry.Timestamp = UnixTimestamp.Empty;
   157              entry.Text = null;
   158              entry.Text = string.Format("[{0}] {1}", _tag, line.Trim());
   159              entry.Timestamp = time;
   160          }
   161  
   162          public void WriteTo(TextWriter writer)
   163          {
   164              var start = _log.NextIndex;
   165              for (UInt32 i = 0; i < _log.LineCount; ++i)
   166              {
   167                  var entry = _log[i + start];
   168                  if (entry.Timestamp.IsEmpty)
   169                      continue;
   170                  var text = entry.ToString();
   171                  if (text == null)
   172                      continue;
   173                  writer.WriteLine(text);
   174              }
   175          }
   176  
   177          public static readonly UInt32 CursorAll = UInt32.MaxValue;
   178          public List<string> FollowFromCursor(ref UInt32 cursor)
   179          {
   180              var lines = new List<string>((int)_log.LineCount);
   181              var i = cursor;
   182              var all = cursor == CursorAll;
   183              if (all)
   184                  i = _log.NextIndex;
   185              for (UInt32 l = 0; l < _log.LineCount; ++l, ++i)
   186              {
   187                  if (!all && i % _log.LineCount == _log.NextIndex % _log.LineCount)
   188                      break;
   189                  var entry = _log[i];
   190                  if (entry.Timestamp.IsEmpty)
   191                  {
   192                      if (all)
   193                          continue;
   194                      break;
   195                  }
   196                  cursor = (i + 1) % _log.LineCount;
   197                  var text = entry.ToString();
   198                  if (text == null)
   199                      continue;
   200                  lines.Add(text);
   201              }
   202              return lines;
   203          }
   204      }
   205  }