Branch data Line data Source code
1 : : /* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited
2 : : * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
3 : : */
4 : :
5 : : #include "SolidSyslogCircularBuffer.h"
6 : :
7 : : #include <stdbool.h>
8 : : #include <stddef.h>
9 : : #include <stdint.h>
10 : : #include <string.h>
11 : :
12 : : #include "SolidSyslogBufferCategories.h"
13 : : #include "SolidSyslogBufferDefinition.h"
14 : : #include "SolidSyslogCircularBufferErrors.h"
15 : : #include "SolidSyslogCircularBufferPrivate.h"
16 : : #include "SolidSyslogError.h"
17 : : #include "SolidSyslogErrorCategory.h"
18 : : #include "SolidSyslogMutex.h"
19 : : #include "SolidSyslogNullBuffer.h"
20 : : #include "SolidSyslogPrival.h"
21 : : #include "SolidSyslogTunables.h"
22 : :
23 : : const struct SolidSyslogErrorSource SolidSyslogCircularBufferErrorSource = {
24 : : "CircularBuffer",
25 : : SolidSyslogCircularBufferErrors_Name
26 : : };
27 : :
28 : : enum
29 : : {
30 : : HEADER_BYTES = SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES
31 : : };
32 : :
33 : : /* Which run of losses, if any, a write has just started. */
34 : : enum SolidSyslogCircularBufferRunStarted
35 : : {
36 : : RUN_STARTED_NONE,
37 : : RUN_STARTED_REFUSALS,
38 : : RUN_STARTED_EVICTIONS
39 : : };
40 : :
41 : : static bool CircularBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead);
42 : : static void CircularBuffer_Write(struct SolidSyslogBuffer* base, const void* data, size_t size);
43 : : static bool CircularBuffer_IsFull(struct SolidSyslogBuffer* base);
44 : :
45 : : static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base);
46 : : static inline void CircularBuffer_InstallDiscardPolicy(
47 : : struct SolidSyslogCircularBuffer* self,
48 : : SolidSyslogDiscardPolicyFunction configured,
49 : : void* context
50 : : );
51 : :
52 : : static inline void CircularBuffer_ReportRecordTooLarge(void);
53 : : static enum SolidSyslogCircularBufferRunStarted CircularBuffer_StoreApplyingPolicy(
54 : : struct SolidSyslogCircularBuffer* self,
55 : : enum SolidSyslogDiscardPolicy policy,
56 : : const void* data,
57 : : size_t size
58 : : );
59 : : static inline bool CircularBuffer_PolicyEvictsFor(
60 : : const struct SolidSyslogCircularBuffer* self,
61 : : size_t recordBytes,
62 : : enum SolidSyslogDiscardPolicy policy
63 : : );
64 : : static inline bool CircularBuffer_CouldHoldWhenEmpty(const struct SolidSyslogCircularBuffer* self, size_t recordBytes);
65 : : static inline void CircularBuffer_EvictUntilRecordFits(struct SolidSyslogCircularBuffer* self, size_t recordBytes);
66 : : static inline void CircularBuffer_DropHead(struct SolidSyslogCircularBuffer* self);
67 : : static inline enum SolidSyslogCircularBufferRunStarted CircularBuffer_KeepRecord(
68 : : struct SolidSyslogCircularBuffer* self,
69 : : const void* data,
70 : : size_t size,
71 : : bool evicted
72 : : );
73 : : static inline enum SolidSyslogCircularBufferRunStarted CircularBuffer_RefuseRecord(
74 : : struct SolidSyslogCircularBuffer* self,
75 : : size_t recordBytes
76 : : );
77 : : static void CircularBuffer_ReportRunStarted(enum SolidSyslogCircularBufferRunStarted started);
78 : : static inline void CircularBuffer_ReportRecordExceedsMaxMessageSize(void);
79 : :
80 : : static inline bool CircularBuffer_IsEmpty(const struct SolidSyslogCircularBuffer* self);
81 : : static inline bool CircularBuffer_IsWrapped(const struct SolidSyslogCircularBuffer* self);
82 : : static inline bool CircularBuffer_HeadAtWrapPoint(const struct SolidSyslogCircularBuffer* self);
83 : : static inline bool CircularBuffer_RecordFits(const struct SolidSyslogCircularBuffer* self, size_t recordBytes);
84 : : static inline bool CircularBuffer_RecordFitsAtTail(const struct SolidSyslogCircularBuffer* self, size_t recordBytes);
85 : : static inline bool CircularBuffer_RecordFitsAfterWrap(const struct SolidSyslogCircularBuffer* self, size_t recordBytes);
86 : : static inline void CircularBuffer_ResetToStart(struct SolidSyslogCircularBuffer* self);
87 : : static inline void CircularBuffer_WrapTail(struct SolidSyslogCircularBuffer* self);
88 : : static inline void CircularBuffer_ConsumeWrapMarker(struct SolidSyslogCircularBuffer* self);
89 : : static inline void CircularBuffer_StoreRecord(struct SolidSyslogCircularBuffer* self, const void* data, size_t size);
90 : : static inline void CircularBuffer_LoadRecord(struct SolidSyslogCircularBuffer* self, void* data, size_t* bytesRead);
91 : : static inline size_t CircularBuffer_PeekRecordSize(const struct SolidSyslogCircularBuffer* self);
92 : :
93 : 234 : void SolidSyslogCircularBuffer_Initialise(
94 : : struct SolidSyslogBuffer* base,
95 : : struct SolidSyslogMutex* mutex,
96 : : uint8_t* ring,
97 : : size_t ringBytes,
98 : : SolidSyslogDiscardPolicyFunction getDiscardPolicy,
99 : : void* discardPolicyContext
100 : : )
101 : : {
102 : 234 : struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
103 : 234 : self->Base.Read = CircularBuffer_Read;
104 : 234 : self->Base.Write = CircularBuffer_Write;
105 : 234 : self->Base.IsFull = CircularBuffer_IsFull;
106 : 234 : self->Mutex = mutex;
107 : 234 : self->Ring = ring;
108 : 234 : self->Capacity = ringBytes;
109 : 234 : CircularBuffer_InstallDiscardPolicy(self, getDiscardPolicy, discardPolicyContext);
110 : 234 : self->LossReported = false;
111 : 234 : self->EvictionReported = false;
112 : 234 : self->TooLargeReported = false;
113 : 234 : self->Full = false;
114 : 234 : CircularBuffer_ResetToStart(self);
115 : 234 : }
116 : :
117 : 234 : static inline void CircularBuffer_InstallDiscardPolicy(
118 : : struct SolidSyslogCircularBuffer* self,
119 : : SolidSyslogDiscardPolicyFunction configured,
120 : : void* context
121 : : )
122 : : {
123 : 234 : self->GetDiscardPolicy = SolidSyslogDiscardPolicy_Default;
124 : 234 : self->DiscardPolicyContext = NULL;
125 [ + + ]: 234 : if (configured != NULL)
126 : : {
127 : 215 : self->GetDiscardPolicy = configured;
128 : 215 : self->DiscardPolicyContext = context;
129 : : }
130 : 234 : }
131 : :
132 : 234 : void SolidSyslogCircularBuffer_Cleanup(struct SolidSyslogBuffer* base)
133 : : {
134 : : /* Overwrite the abstract base with the shared NullBuffer vtable so
135 : : * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. Derived
136 : : * fields are private to this TU; the next SolidSyslogCircularBuffer_Initialise overwrites them. */
137 : 234 : *base = *SolidSyslogNullBuffer_Get();
138 : 234 : }
139 : :
140 : 8574 : static bool CircularBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead)
141 : : {
142 : 8574 : struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
143 : 8574 : bool tooLargeRunStarted = false;
144 : 8574 : SolidSyslogMutex_Lock(self->Mutex);
145 : 8574 : *bytesRead = 0;
146 : 8574 : bool delivered = !CircularBuffer_IsEmpty(self);
147 [ + + ]: 8574 : if (delivered)
148 : : {
149 [ + + ]: 4916 : if (CircularBuffer_HeadAtWrapPoint(self))
150 : : {
151 : 12 : CircularBuffer_ConsumeWrapMarker(self);
152 : : }
153 [ + + ]: 4916 : if (CircularBuffer_PeekRecordSize(self) <= maxSize)
154 : : {
155 : 4902 : CircularBuffer_LoadRecord(self, data, bytesRead);
156 : 4902 : self->Full = false;
157 : 4902 : self->TooLargeReported = false;
158 : : }
159 : : else
160 : : {
161 : 14 : tooLargeRunStarted = !self->TooLargeReported;
162 : 14 : self->TooLargeReported = true;
163 : 14 : delivered = false;
164 : : }
165 : : }
166 : 8574 : SolidSyslogMutex_Unlock(self->Mutex);
167 : :
168 : : /* Reported after the unlock: the handler is integrator code and may log,
169 : : * which re-enters Write and would deadlock on this same non-recursive mutex.
170 : : * The oversized branch changes only its latch, set under the mutex, so
171 : : * deferring the report costs nothing. */
172 [ + + ]: 8574 : if (tooLargeRunStarted)
173 : : {
174 : 8 : CircularBuffer_ReportRecordTooLarge();
175 : : }
176 : 8574 : return delivered;
177 : : }
178 : :
179 : 8 : static inline void CircularBuffer_ReportRecordTooLarge(void)
180 : : {
181 : 8 : CircularBuffer_Report(
182 : : SOLIDSYSLOG_SEVERITY_ERROR,
183 : : SOLIDSYSLOG_CAT_BUFFER_BACKEND_FAILED,
184 : : SOLIDSYSLOG_CIRCULAR_BUFFER_ERROR_RECORD_TOO_LARGE
185 : : );
186 : 8 : }
187 : :
188 : 16073 : static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base)
189 : : {
190 : 16073 : return (struct SolidSyslogCircularBuffer*) base;
191 : : }
192 : :
193 : 15962 : static inline bool CircularBuffer_IsEmpty(const struct SolidSyslogCircularBuffer* self)
194 : : {
195 : 15962 : return self->Head == self->Tail;
196 : : }
197 : :
198 : 5650 : static inline bool CircularBuffer_HeadAtWrapPoint(const struct SolidSyslogCircularBuffer* self)
199 : : {
200 : 5650 : return self->Head >= self->WrapPoint;
201 : : }
202 : :
203 : 31 : static inline void CircularBuffer_ConsumeWrapMarker(struct SolidSyslogCircularBuffer* self)
204 : : {
205 : 31 : self->Head = 0;
206 : 31 : self->WrapPoint = self->Capacity;
207 : 31 : }
208 : :
209 : 10552 : static inline size_t CircularBuffer_PeekRecordSize(const struct SolidSyslogCircularBuffer* self)
210 : : {
211 : 10552 : return ((size_t) self->Ring[self->Head]) | (((size_t) self->Ring[self->Head + 1U]) << 8U);
212 : : }
213 : :
214 : 4902 : static inline void CircularBuffer_LoadRecord(struct SolidSyslogCircularBuffer* self, void* data, size_t* bytesRead)
215 : : {
216 : 4902 : size_t recordSize = CircularBuffer_PeekRecordSize(self);
217 : 4902 : (void) memcpy(data, &self->Ring[self->Head + HEADER_BYTES], recordSize);
218 : 4902 : self->Head += HEADER_BYTES + recordSize;
219 : 4902 : *bytesRead = recordSize;
220 : 4902 : }
221 : :
222 : 6656 : static void CircularBuffer_Write(struct SolidSyslogBuffer* base, const void* data, size_t size)
223 : : {
224 : 6656 : struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
225 [ + + ]: 6656 : if (size <= SOLIDSYSLOG_MAX_MESSAGE_SIZE)
226 : : {
227 : : /* The policy is asked before the lock and the loss reported after it,
228 : : * as Read reports: both are integrator code that may log, which
229 : : * re-enters Write. */
230 : 6654 : enum SolidSyslogDiscardPolicy policy = self->GetDiscardPolicy(self->DiscardPolicyContext);
231 : 6654 : enum SolidSyslogCircularBufferRunStarted started = CircularBuffer_StoreApplyingPolicy(self, policy, data, size);
232 : 6654 : CircularBuffer_ReportRunStarted(started);
233 : : }
234 : : else
235 : : {
236 : 2 : CircularBuffer_ReportRecordExceedsMaxMessageSize();
237 : : }
238 : 6656 : }
239 : :
240 : : /* Stores the record, making room for it first where the discard policy says
241 : : * to, and says which run of losses, if any, this write starts: the one write
242 : : * of each run that is reported. A run is marked under the lock, so a write
243 : : * from the handler reporting it finds it already reported. */
244 : 6654 : static enum SolidSyslogCircularBufferRunStarted CircularBuffer_StoreApplyingPolicy(
245 : : struct SolidSyslogCircularBuffer* self,
246 : : enum SolidSyslogDiscardPolicy policy,
247 : : const void* data,
248 : : size_t size
249 : : )
250 : : {
251 : 6654 : enum SolidSyslogCircularBufferRunStarted started = RUN_STARTED_NONE;
252 : 6654 : SolidSyslogMutex_Lock(self->Mutex);
253 [ + + ]: 6654 : if (CircularBuffer_IsEmpty(self))
254 : : {
255 : 2320 : CircularBuffer_ResetToStart(self);
256 : : }
257 : 6654 : size_t recordBytes = HEADER_BYTES + size;
258 : 6654 : bool evicted = false;
259 [ + + + + ]: 6654 : if (!CircularBuffer_RecordFits(self, recordBytes) && CircularBuffer_PolicyEvictsFor(self, recordBytes, policy))
260 : : {
261 : 718 : CircularBuffer_EvictUntilRecordFits(self, recordBytes);
262 : 718 : evicted = true;
263 : : }
264 [ + + ]: 6654 : if (CircularBuffer_RecordFits(self, recordBytes))
265 : : {
266 : 5737 : started = CircularBuffer_KeepRecord(self, data, size, evicted);
267 : : }
268 : : else
269 : : {
270 : 917 : started = CircularBuffer_RefuseRecord(self, recordBytes);
271 : : }
272 : 6654 : SolidSyslogMutex_Unlock(self->Mutex);
273 : 6654 : return started;
274 : : }
275 : :
276 : : /* Only Oldest makes room, and only for a record the ring could hold at all:
277 : : * evicting everything for one that still would not fit loses records for
278 : : * nothing. */
279 : 1635 : static inline bool CircularBuffer_PolicyEvictsFor(
280 : : const struct SolidSyslogCircularBuffer* self,
281 : : size_t recordBytes,
282 : : enum SolidSyslogDiscardPolicy policy
283 : : )
284 : : {
285 [ + + + + ]: 1635 : return CircularBuffer_CouldHoldWhenEmpty(self, recordBytes) && (policy == SOLIDSYSLOG_DISCARD_POLICY_OLDEST);
286 : : }
287 : :
288 : 2552 : static inline bool CircularBuffer_CouldHoldWhenEmpty(const struct SolidSyslogCircularBuffer* self, size_t recordBytes)
289 : : {
290 : 2552 : return recordBytes <= self->Capacity;
291 : : }
292 : :
293 : : /* Drops records from the head until the new one fits; an emptied ring always
294 : : * fits a record it could hold at all, so this ends. */
295 : 718 : static inline void CircularBuffer_EvictUntilRecordFits(struct SolidSyslogCircularBuffer* self, size_t recordBytes)
296 : : {
297 [ + + ]: 1452 : while (!CircularBuffer_RecordFits(self, recordBytes))
298 : : {
299 : 734 : CircularBuffer_DropHead(self);
300 : : }
301 : 718 : }
302 : :
303 : : /* Skips the head record as Read would take it, without copying it out. */
304 : 734 : static inline void CircularBuffer_DropHead(struct SolidSyslogCircularBuffer* self)
305 : : {
306 [ + + ]: 734 : if (CircularBuffer_HeadAtWrapPoint(self))
307 : : {
308 : 19 : CircularBuffer_ConsumeWrapMarker(self);
309 : : }
310 : 734 : self->Head += HEADER_BYTES + CircularBuffer_PeekRecordSize(self);
311 [ + + ]: 734 : if (CircularBuffer_IsEmpty(self))
312 : : {
313 : 10 : CircularBuffer_ResetToStart(self);
314 : : }
315 : 734 : }
316 : :
317 : : /* A record kept ends a run of refusals; one kept without evicting ends a run
318 : : * of evictions. The ring is full afterwards only if it had to make room. */
319 : 5737 : static inline enum SolidSyslogCircularBufferRunStarted CircularBuffer_KeepRecord(
320 : : struct SolidSyslogCircularBuffer* self,
321 : : const void* data,
322 : : size_t size,
323 : : bool evicted
324 : : )
325 : : {
326 : 5737 : enum SolidSyslogCircularBufferRunStarted started = RUN_STARTED_NONE;
327 [ + + ]: 5737 : if (!CircularBuffer_RecordFitsAtTail(self, HEADER_BYTES + size))
328 : : {
329 : 33 : CircularBuffer_WrapTail(self);
330 : : }
331 : 5737 : CircularBuffer_StoreRecord(self, data, size);
332 : 5737 : self->LossReported = false;
333 [ + + + + ]: 5737 : if (evicted && !self->EvictionReported)
334 : : {
335 : 29 : started = RUN_STARTED_EVICTIONS;
336 : : }
337 : 5737 : self->EvictionReported = evicted;
338 : 5737 : self->Full = evicted;
339 : 5737 : return started;
340 : : }
341 : :
342 : : /* A refusal evicts nothing, so it ends a run of evictions. It leaves the ring
343 : : * full unless the record could never have fitted, which is a configuration
344 : : * fault rather than Log outrunning Service. */
345 : 917 : static inline enum SolidSyslogCircularBufferRunStarted CircularBuffer_RefuseRecord(
346 : : struct SolidSyslogCircularBuffer* self,
347 : : size_t recordBytes
348 : : )
349 : : {
350 : 917 : enum SolidSyslogCircularBufferRunStarted started = self->LossReported ? RUN_STARTED_NONE : RUN_STARTED_REFUSALS;
351 : 917 : self->LossReported = true;
352 : 917 : self->EvictionReported = false;
353 : 917 : self->Full = CircularBuffer_CouldHoldWhenEmpty(self, recordBytes);
354 : 917 : return started;
355 : : }
356 : :
357 : 6654 : static void CircularBuffer_ReportRunStarted(enum SolidSyslogCircularBufferRunStarted started)
358 : : {
359 [ + + ]: 6654 : if (started == RUN_STARTED_REFUSALS)
360 : : {
361 : 52 : CircularBuffer_Report(
362 : : SOLIDSYSLOG_SEVERITY_WARNING,
363 : : SOLIDSYSLOG_CAT_BUFFER_RECORDS_LOST,
364 : : SOLIDSYSLOG_CIRCULAR_BUFFER_ERROR_RECORD_REFUSED
365 : : );
366 : : }
367 [ + + ]: 6602 : else if (started == RUN_STARTED_EVICTIONS)
368 : : {
369 : 29 : CircularBuffer_Report(
370 : : SOLIDSYSLOG_SEVERITY_WARNING,
371 : : SOLIDSYSLOG_CAT_BUFFER_RECORDS_LOST,
372 : : SOLIDSYSLOG_CIRCULAR_BUFFER_ERROR_RECORDS_EVICTED
373 : : );
374 : : }
375 : : else
376 : : {
377 : : /* Kept without loss, or a run already reported. */
378 : : }
379 : 6654 : }
380 : :
381 : 609 : static bool CircularBuffer_IsFull(struct SolidSyslogBuffer* base)
382 : : {
383 : 609 : struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
384 : 609 : SolidSyslogMutex_Lock(self->Mutex);
385 : 609 : bool full = self->Full;
386 : 609 : SolidSyslogMutex_Unlock(self->Mutex);
387 : 609 : return full;
388 : : }
389 : :
390 : : /* SolidSyslog_Log never formats a message this large, so only a caller writing
391 : : * to the buffer directly can hand one over. */
392 : 2 : static inline void CircularBuffer_ReportRecordExceedsMaxMessageSize(void)
393 : : {
394 : 2 : CircularBuffer_Report(
395 : : SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
396 : : SOLIDSYSLOG_CAT_BAD_ARGUMENT,
397 : : SOLIDSYSLOG_CIRCULAR_BUFFER_ERROR_RECORD_EXCEEDS_MAX_MESSAGE_SIZE
398 : : );
399 : 2 : }
400 : :
401 : 23849 : static inline bool CircularBuffer_IsWrapped(const struct SolidSyslogCircularBuffer* self)
402 : : {
403 : 23849 : return self->Head > self->Tail;
404 : : }
405 : :
406 : 14760 : static inline bool CircularBuffer_RecordFits(const struct SolidSyslogCircularBuffer* self, size_t recordBytes)
407 : : {
408 [ + + + + ]: 14760 : return CircularBuffer_RecordFitsAtTail(self, recordBytes) || CircularBuffer_RecordFitsAfterWrap(self, recordBytes);
409 : : }
410 : :
411 : 20497 : static inline bool CircularBuffer_RecordFitsAtTail(const struct SolidSyslogCircularBuffer* self, size_t recordBytes)
412 : : {
413 : 20497 : bool fits = false;
414 [ + + ]: 20497 : if (CircularBuffer_IsWrapped(self))
415 : : {
416 : 3418 : fits = (self->Tail + recordBytes) < self->Head;
417 : : }
418 : : else
419 : : {
420 : 17079 : fits = (self->Tail + recordBytes) <= self->Capacity;
421 : : }
422 : 20497 : return fits;
423 : : }
424 : :
425 : 3352 : static inline bool CircularBuffer_RecordFitsAfterWrap(const struct SolidSyslogCircularBuffer* self, size_t recordBytes)
426 : : {
427 [ + + + + ]: 3352 : return (!CircularBuffer_IsWrapped(self)) && (recordBytes < self->Head);
428 : : }
429 : :
430 : 2564 : static inline void CircularBuffer_ResetToStart(struct SolidSyslogCircularBuffer* self)
431 : : {
432 : 2564 : self->Head = 0;
433 : 2564 : self->Tail = 0;
434 : 2564 : self->WrapPoint = self->Capacity;
435 : 2564 : }
436 : :
437 : 33 : static inline void CircularBuffer_WrapTail(struct SolidSyslogCircularBuffer* self)
438 : : {
439 : 33 : self->WrapPoint = self->Tail;
440 : 33 : self->Tail = 0;
441 : 33 : }
442 : :
443 : 5737 : static inline void CircularBuffer_StoreRecord(struct SolidSyslogCircularBuffer* self, const void* data, size_t size)
444 : : {
445 : 5737 : self->Ring[self->Tail] = (uint8_t) (size & 0xFFU);
446 : 5737 : self->Ring[self->Tail + 1U] = (uint8_t) ((size >> 8U) & 0xFFU);
447 : 5737 : (void) memcpy(&self->Ring[self->Tail + HEADER_BYTES], data, size);
448 : 5737 : self->Tail += HEADER_BYTES + size;
449 : 5737 : }
|