golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ringlogger/ringlogger.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package ringlogger
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"runtime"
    14  	"strconv"
    15  	"sync/atomic"
    16  	"time"
    17  	"unsafe"
    18  
    19  	"golang.org/x/sys/windows"
    20  )
    21  
    22  const (
    23  	maxLogLineLength = 512
    24  	maxTagLength     = 5
    25  	maxLines         = 2048
    26  	magic            = 0xbadbabe
    27  )
    28  
    29  type logLine struct {
    30  	timeNs int64
    31  	line   [maxLogLineLength]byte
    32  }
    33  
    34  type logMem struct {
    35  	magic     uint32
    36  	nextIndex uint32
    37  	lines     [maxLines]logLine
    38  }
    39  
    40  type Ringlogger struct {
    41  	tag      string
    42  	file     *os.File
    43  	mapping  windows.Handle
    44  	log      *logMem
    45  	readOnly bool
    46  }
    47  
    48  func NewRinglogger(filename, tag string) (*Ringlogger, error) {
    49  	if len(tag) > maxTagLength {
    50  		return nil, windows.ERROR_LABEL_TOO_LONG
    51  	}
    52  	file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o600)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	err = file.Truncate(int64(unsafe.Sizeof(logMem{})))
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
    61  	if err != nil && err != windows.ERROR_ALREADY_EXISTS {
    62  		return nil, err
    63  	}
    64  	rl, err := newRingloggerFromMappingHandle(mapping, tag, windows.FILE_MAP_WRITE)
    65  	if err != nil {
    66  		windows.CloseHandle(mapping)
    67  		return nil, err
    68  	}
    69  	rl.file = file
    70  	return rl, nil
    71  }
    72  
    73  func NewRingloggerFromInheritedMappingHandle(handleStr, tag string) (*Ringlogger, error) {
    74  	handle, err := strconv.ParseUint(handleStr, 10, 64)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	return newRingloggerFromMappingHandle(windows.Handle(handle), tag, windows.FILE_MAP_READ)
    79  }
    80  
    81  func newRingloggerFromMappingHandle(mappingHandle windows.Handle, tag string, access uint32) (*Ringlogger, error) {
    82  	view, err := windows.MapViewOfFile(mappingHandle, access, 0, 0, 0)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	log := (*logMem)(unsafe.Pointer(view))
    87  	if log.magic != magic {
    88  		bytes := (*[unsafe.Sizeof(logMem{})]byte)(unsafe.Pointer(log))
    89  		for i := range bytes {
    90  			bytes[i] = 0
    91  		}
    92  		log.magic = magic
    93  		windows.FlushViewOfFile(view, uintptr(len(bytes)))
    94  	}
    95  
    96  	rl := &Ringlogger{
    97  		tag:      tag,
    98  		mapping:  mappingHandle,
    99  		log:      log,
   100  		readOnly: access&windows.FILE_MAP_WRITE == 0,
   101  	}
   102  	runtime.SetFinalizer(rl, (*Ringlogger).Close)
   103  	return rl, nil
   104  }
   105  
   106  func (rl *Ringlogger) Write(p []byte) (n int, err error) {
   107  	// Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
   108  	ts := time.Now().UnixNano()
   109  	return rl.WriteWithTimestamp(p, ts)
   110  }
   111  
   112  func (rl *Ringlogger) WriteWithTimestamp(p []byte, ts int64) (n int, err error) {
   113  	if rl.readOnly {
   114  		return 0, io.ErrShortWrite
   115  	}
   116  	ret := len(p)
   117  	p = bytes.TrimSpace(p)
   118  	if len(p) == 0 {
   119  		return ret, nil
   120  	}
   121  
   122  	if rl.log == nil {
   123  		return 0, io.EOF
   124  	}
   125  
   126  	// Race: More than maxLines writers and this will clash.
   127  	index := atomic.AddUint32(&rl.log.nextIndex, 1) - 1
   128  	line := &rl.log.lines[index%maxLines]
   129  
   130  	// Race: Before this line executes, we'll display old data after new data.
   131  	atomic.StoreInt64(&line.timeNs, 0)
   132  	for i := range line.line {
   133  		line.line[i] = 0
   134  	}
   135  
   136  	textLen := 3 + len(p) + len(rl.tag)
   137  	if textLen > maxLogLineLength-1 {
   138  		p = p[:maxLogLineLength-1-3-len(rl.tag)]
   139  		textLen = maxLogLineLength - 1
   140  	}
   141  	line.line[textLen] = 0
   142  	line.line[0] = 0 // Null out the beginning and only let it extend after the other writes have completed
   143  	copy(line.line[1:], rl.tag)
   144  	line.line[1+len(rl.tag)] = ']'
   145  	line.line[2+len(rl.tag)] = ' '
   146  	copy(line.line[3+len(rl.tag):], p[:])
   147  	line.line[0] = '['
   148  	atomic.StoreInt64(&line.timeNs, ts)
   149  
   150  	return ret, nil
   151  }
   152  
   153  func (rl *Ringlogger) WriteTo(out io.Writer) (n int64, err error) {
   154  	if rl.log == nil {
   155  		return 0, io.EOF
   156  	}
   157  	log := *rl.log
   158  	i := log.nextIndex
   159  	for l := uint32(0); l < maxLines; l++ {
   160  		line := &log.lines[(i+l)%maxLines]
   161  		if line.timeNs == 0 {
   162  			continue
   163  		}
   164  		index := bytes.IndexByte(line.line[:], 0)
   165  		if index < 1 {
   166  			continue
   167  		}
   168  		var bytes int
   169  		bytes, err = fmt.Fprintf(out, "%s: %s\n", time.Unix(0, line.timeNs).Format("2006-01-02 15:04:05.000000"), line.line[:index])
   170  		if err != nil {
   171  			return
   172  		}
   173  		n += int64(bytes)
   174  	}
   175  	return
   176  }
   177  
   178  const CursorAll = ^uint32(0)
   179  
   180  type FollowLine struct {
   181  	Line  string
   182  	Stamp time.Time
   183  }
   184  
   185  func (rl *Ringlogger) FollowFromCursor(cursor uint32) (followLines []FollowLine, nextCursor uint32) {
   186  	followLines = make([]FollowLine, 0, maxLines)
   187  	nextCursor = cursor
   188  
   189  	if rl.log == nil {
   190  		return
   191  	}
   192  	log := *rl.log
   193  
   194  	i := cursor
   195  	if cursor == CursorAll {
   196  		i = log.nextIndex
   197  	}
   198  
   199  	for l := 0; l < maxLines; l++ {
   200  		line := &log.lines[i%maxLines]
   201  		if cursor != CursorAll && i%maxLines == log.nextIndex%maxLines {
   202  			break
   203  		}
   204  		if line.timeNs == 0 {
   205  			if cursor == CursorAll {
   206  				i++
   207  				continue
   208  			} else {
   209  				break
   210  			}
   211  		}
   212  		index := bytes.IndexByte(line.line[:], 0)
   213  		if index > 0 {
   214  			followLines = append(followLines, FollowLine{string(line.line[:index]), time.Unix(0, line.timeNs)})
   215  		}
   216  		i++
   217  		nextCursor = i % maxLines
   218  	}
   219  	return
   220  }
   221  
   222  func (rl *Ringlogger) Close() error {
   223  	if rl.file != nil {
   224  		rl.file.Close()
   225  		rl.file = nil
   226  	}
   227  	if rl.log != nil {
   228  		windows.UnmapViewOfFile((uintptr)(unsafe.Pointer(rl.log)))
   229  		rl.log = nil
   230  	}
   231  	if rl.mapping != 0 {
   232  		windows.CloseHandle(rl.mapping)
   233  		rl.mapping = 0
   234  	}
   235  	return nil
   236  }
   237  
   238  func (rl *Ringlogger) ExportInheritableMappingHandle() (handleToClose windows.Handle, err error) {
   239  	handleToClose, err = windows.CreateFileMapping(windows.Handle(rl.file.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil)
   240  	if err != nil && err != windows.ERROR_ALREADY_EXISTS {
   241  		return
   242  	}
   243  	err = windows.SetHandleInformation(handleToClose, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
   244  	if err != nil {
   245  		windows.CloseHandle(handleToClose)
   246  		handleToClose = 0
   247  		return
   248  	}
   249  	return
   250  }