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() {}