github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/journald/internal/sdjournal/sdjournal.go (about)

     1  //go:build linux && cgo && !static_build && journald
     2  // +build linux,cgo,!static_build,journald
     3  
     4  package sdjournal // import "github.com/docker/docker/daemon/logger/journald/internal/sdjournal"
     5  
     6  // #cgo pkg-config: libsystemd
     7  // #include <stdlib.h>
     8  // #include <systemd/sd-journal.h>
     9  //
    10  // static int add_match(sd_journal *j, _GoString_ s) {
    11  // 	return sd_journal_add_match(j, _GoStringPtr(s), _GoStringLen(s));
    12  // }
    13  import "C"
    14  import (
    15  	"fmt"
    16  	"runtime"
    17  	"strings"
    18  	"syscall"
    19  	"time"
    20  	"unsafe"
    21  )
    22  
    23  // Status is an sd-journal status code.
    24  type Status int
    25  
    26  // Status values for Process() and Wait().
    27  const (
    28  	StatusNOP        = Status(C.SD_JOURNAL_NOP)        // SD_JOURNAL_NOP
    29  	StatusAPPEND     = Status(C.SD_JOURNAL_APPEND)     // SD_JOURNAL_APPEND
    30  	StatusINVALIDATE = Status(C.SD_JOURNAL_INVALIDATE) // SD_JOURNAL_INVALIDATE
    31  )
    32  
    33  const (
    34  	// ErrInvalidReadPointer is the error returned when the read pointer is
    35  	// in an invalid position.
    36  	ErrInvalidReadPointer = syscall.EADDRNOTAVAIL
    37  )
    38  
    39  // Journal is a handle to an open journald journal.
    40  type Journal struct {
    41  	j      *C.sd_journal
    42  	noCopy noCopy //nolint:unused // Exists only to mark values uncopyable for `go vet`.
    43  }
    44  
    45  // Open opens the log journal for reading.
    46  //
    47  // The returned Journal value may only be used from the same operating system
    48  // thread which Open was called from. Using it from only a single goroutine is
    49  // not sufficient; runtime.LockOSThread must also be used.
    50  func Open(flags int) (*Journal, error) {
    51  	j := &Journal{}
    52  	if rc := C.sd_journal_open(&j.j, C.int(flags)); rc != 0 {
    53  		return nil, fmt.Errorf("journald: error opening journal: %w", syscall.Errno(-rc))
    54  	}
    55  	runtime.SetFinalizer(j, (*Journal).Close)
    56  	return j, nil
    57  }
    58  
    59  // OpenDir opens the journal files at the specified absolute directory path for
    60  // reading.
    61  //
    62  // The returned Journal value may only be used from the same operating system
    63  // thread which Open was called from. Using it from only a single goroutine is
    64  // not sufficient; runtime.LockOSThread must also be used.
    65  func OpenDir(path string, flags int) (*Journal, error) {
    66  	j := &Journal{}
    67  	cpath := C.CString(path)
    68  	defer C.free(unsafe.Pointer(cpath))
    69  	if rc := C.sd_journal_open_directory(&j.j, cpath, C.int(flags)); rc != 0 {
    70  		return nil, fmt.Errorf("journald: error opening journal: %w", syscall.Errno(-rc))
    71  	}
    72  	runtime.SetFinalizer(j, (*Journal).Close)
    73  	return j, nil
    74  }
    75  
    76  // Close closes the journal. The return value is always nil.
    77  func (j *Journal) Close() error {
    78  	if j.j != nil {
    79  		C.sd_journal_close(j.j)
    80  		runtime.SetFinalizer(j, nil)
    81  		j.j = nil
    82  	}
    83  	return nil
    84  }
    85  
    86  // Process processes journal events.
    87  //
    88  // https://www.freedesktop.org/software/systemd/man/sd_journal_process.html
    89  func (j *Journal) Process() (Status, error) {
    90  	s := C.sd_journal_process(j.j)
    91  	if s < 0 {
    92  		return 0, fmt.Errorf("journald: error processing events: %w", syscall.Errno(-s))
    93  	}
    94  	return Status(s), nil
    95  }
    96  
    97  // InitializeInotify sets up change notifications for the journal.
    98  func (j *Journal) InitializeInotify() error {
    99  	if rc := C.sd_journal_get_fd(j.j); rc < 0 {
   100  		return fmt.Errorf("journald: error initializing inotify watches: %w", syscall.Errno(-rc))
   101  	}
   102  	return nil
   103  }
   104  
   105  // AddMatch adds a match by which to filter the entries of the journal file.
   106  //
   107  // https://www.freedesktop.org/software/systemd/man/sd_journal_add_match.html
   108  func (j *Journal) AddMatch(field, value string) error {
   109  	m := field + "=" + value
   110  	if rc := C.add_match(j.j, m); rc != 0 {
   111  		return fmt.Errorf("journald: error adding match %q: %w", m, syscall.Errno(-rc))
   112  	}
   113  	return nil
   114  }
   115  
   116  // Next advances the read pointer to the next entry.
   117  func (j *Journal) Next() (bool, error) {
   118  	rc := C.sd_journal_next(j.j)
   119  	if rc < 0 {
   120  		return false, fmt.Errorf("journald: error advancing read pointer: %w", syscall.Errno(-rc))
   121  	}
   122  	return rc > 0, nil
   123  }
   124  
   125  // Previous sets back the read pointer to the previous entry.
   126  func (j *Journal) Previous() (bool, error) {
   127  	rc := C.sd_journal_previous(j.j)
   128  	if rc < 0 {
   129  		return false, fmt.Errorf("journald: error setting back read pointer: %w", syscall.Errno(-rc))
   130  	}
   131  	return rc > 0, nil
   132  }
   133  
   134  // PreviousSkip sets back the read pointer by skip entries, returning the number
   135  // of entries set back. skip must be less than or equal to 2147483647
   136  // (2**31 - 1).
   137  //
   138  // skip == 0 is a special case: PreviousSkip(0) resolves the read pointer to a
   139  // discrete position without setting it back to a different entry. The trouble
   140  // is, it always returns zero on recent libsystemd versions. There is no way to
   141  // tell from the return values whether or not it successfully resolved the read
   142  // pointer to a discrete entry.
   143  // https://github.com/systemd/systemd/pull/5930#issuecomment-300878104
   144  func (j *Journal) PreviousSkip(skip uint) (int, error) {
   145  	rc := C.sd_journal_previous_skip(j.j, C.uint64_t(skip))
   146  	if rc < 0 {
   147  		return 0, fmt.Errorf("journald: error setting back read pointer: %w", syscall.Errno(-rc))
   148  	}
   149  	return int(rc), nil
   150  }
   151  
   152  // SeekHead sets the read pointer to the position before the oldest available entry.
   153  //
   154  // BUG: SeekHead() followed by Previous() has unexpected behavior.
   155  // https://github.com/systemd/systemd/issues/17662
   156  func (j *Journal) SeekHead() error {
   157  	if rc := C.sd_journal_seek_head(j.j); rc != 0 {
   158  		return fmt.Errorf("journald: error seeking to head of journal: %w", syscall.Errno(-rc))
   159  	}
   160  	return nil
   161  }
   162  
   163  // SeekTail sets the read pointer to the position after the most recent available entry.
   164  //
   165  // BUG: SeekTail() followed by Next() has unexpected behavior.
   166  // https://github.com/systemd/systemd/issues/9934
   167  func (j *Journal) SeekTail() error {
   168  	if rc := C.sd_journal_seek_tail(j.j); rc != 0 {
   169  		return fmt.Errorf("journald: error seeking to tail of journal: %w", syscall.Errno(-rc))
   170  	}
   171  	return nil
   172  }
   173  
   174  // SeekRealtime seeks to a position with a realtime (wallclock) timestamp after t.
   175  //
   176  // Note that the realtime clock is not necessarily monotonic. If a realtime
   177  // timestamp is ambiguous, the position seeked to is not defined.
   178  func (j *Journal) SeekRealtime(t time.Time) error {
   179  	if rc := C.sd_journal_seek_realtime_usec(j.j, C.uint64_t(t.UnixMicro())); rc != 0 {
   180  		return fmt.Errorf("journald: error seeking to time %v: %w", t, syscall.Errno(-rc))
   181  	}
   182  	return nil
   183  }
   184  
   185  // Wait blocks until the journal gets changed or timeout has elapsed.
   186  // Pass a negative timeout to wait indefinitely.
   187  func (j *Journal) Wait(timeout time.Duration) (Status, error) {
   188  	var dur C.uint64_t
   189  	if timeout < 0 {
   190  		// Wait indefinitely.
   191  		dur = ^C.uint64_t(0) // (uint64_t) -1
   192  	} else {
   193  		dur = C.uint64_t(timeout.Microseconds())
   194  	}
   195  	s := C.sd_journal_wait(j.j, dur)
   196  	if s < 0 {
   197  		return 0, fmt.Errorf("journald: error waiting for event: %w", syscall.Errno(-s))
   198  	}
   199  	return Status(s), nil
   200  }
   201  
   202  // Realtime returns the realtime timestamp of the current journal entry.
   203  func (j *Journal) Realtime() (time.Time, error) {
   204  	var stamp C.uint64_t
   205  	if rc := C.sd_journal_get_realtime_usec(j.j, &stamp); rc != 0 {
   206  		return time.Time{}, fmt.Errorf("journald: error getting journal entry timestamp: %w", syscall.Errno(-rc))
   207  	}
   208  	return time.UnixMicro(int64(stamp)), nil
   209  }
   210  
   211  // Data returns all data fields for the current journal entry.
   212  func (j *Journal) Data() (map[string]string, error) {
   213  	// Copying all the data fields for the entry into a map is more optimal
   214  	// than you might think. Doing so has time complexity O(N), where N is
   215  	// the number of fields in the entry. Looking up a data field in the map
   216  	// is amortized O(1), so the total complexity to look up M data fields
   217  	// is O(N+M). By comparison, looking up a data field using the
   218  	// sd_journal_get_data function has time complexity of O(N) as it is
   219  	// implemented as a linear search through the entry's fields. Therefore
   220  	// looking up M data fields in an entry by calling sd_journal_get_data
   221  	// in a loop would have time complexity of O(N*M).
   222  
   223  	m := make(map[string]string)
   224  	j.restartData()
   225  	for {
   226  		var (
   227  			data unsafe.Pointer
   228  			len  C.size_t
   229  		)
   230  		rc := C.sd_journal_enumerate_data(j.j, &data, &len)
   231  		if rc == 0 {
   232  			return m, nil
   233  		} else if rc < 0 {
   234  			return m, fmt.Errorf("journald: error enumerating entry data: %w", syscall.Errno(-rc))
   235  		}
   236  
   237  		kv := strings.SplitN(C.GoStringN((*C.char)(data), C.int(len)), "=", 2)
   238  		m[kv[0]] = kv[1]
   239  	}
   240  }
   241  
   242  func (j *Journal) restartData() {
   243  	C.sd_journal_restart_data(j.j)
   244  }
   245  
   246  // SetDataThreshold may be used to change the data field size threshold for data
   247  // returned by j.Data(). The threshold is a hint only; larger data fields might
   248  // still be returned.
   249  //
   250  // The default threshold is 64K. To retrieve the complete data fields this
   251  // threshold should be turned off by setting it to 0.
   252  //
   253  // https://www.freedesktop.org/software/systemd/man/sd_journal_set_data_threshold.html
   254  func (j *Journal) SetDataThreshold(v uint) error {
   255  	if rc := C.sd_journal_set_data_threshold(j.j, C.size_t(v)); rc != 0 {
   256  		return fmt.Errorf("journald: error setting journal data threshold: %w", syscall.Errno(-rc))
   257  	}
   258  	return nil
   259  }
   260  
   261  type noCopy struct{}
   262  
   263  func (noCopy) Lock()   {}
   264  func (noCopy) Unlock() {}