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 "SolidSyslogBlockStore.h"
6 : :
7 : : #include <stdbool.h>
8 : : #include <stddef.h>
9 : : #include <stdint.h>
10 : :
11 : : #include "SolidSyslogBlockSequencePrivate.h"
12 : : #include "SolidSyslogRecordStorePrivate.h"
13 : : #include "SolidSyslogBlockDevice.h"
14 : : #include "SolidSyslogBlockStoreErrors.h"
15 : : #include "SolidSyslogBlockStorePrivate.h"
16 : : #include "SolidSyslogError.h"
17 : : #include "SolidSyslogErrorCategory.h"
18 : : #include "SolidSyslogNullSecurityPolicy.h"
19 : : #include "SolidSyslogNullStore.h"
20 : : #include "SolidSyslogPoolAllocator.h"
21 : : #include "SolidSyslogPrival.h"
22 : : #include "SolidSyslogSecurityPolicyDefinition.h"
23 : : #include "SolidSyslogTunables.h"
24 : :
25 : : struct SolidSyslogStore;
26 : :
27 : : static inline size_t BlockStore_IndexFromHandle(const struct SolidSyslogStore* base);
28 : : static inline void BlockStore_CleanupAtIndex(size_t index, void* context);
29 : : static bool BlockStore_IsValidConfig(const struct SolidSyslogBlockStoreConfig* config);
30 : : static struct SolidSyslogSecurityPolicy* BlockStore_ResolveSecurityPolicy(struct SolidSyslogSecurityPolicy* configured);
31 : : static inline bool BlockStore_TrailerFits(const struct SolidSyslogSecurityPolicy* policy);
32 : : static struct SolidSyslogStore* BlockStore_CreateInSlot(
33 : : const struct SolidSyslogBlockStoreConfig* config,
34 : : struct SolidSyslogSecurityPolicy* policy
35 : : );
36 : : static struct SolidSyslogBlockSequenceConfig BlockStore_BuildBlockSequenceConfig(
37 : : const struct SolidSyslogBlockStoreConfig* config,
38 : : const struct SolidSyslogRecordStore* recordStore
39 : : );
40 : : static bool BlockStore_DeviceCanHoldOneRecord(
41 : : const struct SolidSyslogBlockStoreConfig* config,
42 : : const struct SolidSyslogRecordStore* recordStore
43 : : );
44 : :
45 : : static bool BlockStore_InUse[SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE];
46 : : static struct SolidSyslogBlockStore BlockStore_Pool[SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE];
47 : : static struct SolidSyslogPoolAllocator BlockStore_Allocator = {BlockStore_InUse, SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE};
48 : :
49 : 19196 : struct SolidSyslogStore* SolidSyslogBlockStore_Create(const struct SolidSyslogBlockStoreConfig* config)
50 : : {
51 : 19196 : struct SolidSyslogStore* result = SolidSyslogNullStore_Get();
52 : :
53 [ + + ]: 19196 : if (BlockStore_IsValidConfig(config))
54 : : {
55 : 19188 : result = BlockStore_CreateInSlot(config, BlockStore_ResolveSecurityPolicy(config->SecurityPolicy));
56 : : }
57 : :
58 : 19196 : return result;
59 : : }
60 : :
61 : 19196 : static bool BlockStore_IsValidConfig(const struct SolidSyslogBlockStoreConfig* config)
62 : : {
63 : 19196 : bool valid = false;
64 : :
65 [ + + ]: 19196 : if (config == NULL)
66 : : {
67 : 3 : BlockStore_Report(
68 : : SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY,
69 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
70 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_NULL_CONFIG
71 : : );
72 : : }
73 [ + + ]: 19193 : else if (config->BlockDevice == NULL)
74 : : {
75 : 3 : BlockStore_Report(
76 : : SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY,
77 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
78 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_NULL_BLOCK_DEVICE
79 : : );
80 : : }
81 [ + + ]: 19190 : else if (!BlockStore_TrailerFits(BlockStore_ResolveSecurityPolicy(config->SecurityPolicy)))
82 : : {
83 : 2 : BlockStore_Report(
84 : : SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY,
85 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
86 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_TRAILER_TOO_LARGE
87 : : );
88 : : }
89 : : else
90 : : {
91 : 19188 : valid = true;
92 : : }
93 : :
94 : 19196 : return valid;
95 : : }
96 : :
97 : 38378 : static struct SolidSyslogSecurityPolicy* BlockStore_ResolveSecurityPolicy(struct SolidSyslogSecurityPolicy* configured)
98 : : {
99 : 38378 : struct SolidSyslogSecurityPolicy* resolved = configured;
100 : :
101 [ + + ]: 38378 : if (resolved == NULL)
102 : : {
103 : 19242 : resolved = SolidSyslogNullSecurityPolicy_Get();
104 : : }
105 : :
106 : 38378 : return resolved;
107 : : }
108 : :
109 : : /* Every record's trailer is reserved at SOLIDSYSLOG_MAX_INTEGRITY_SIZE, so a policy
110 : : * with a wider one cannot seal into it. Storing records without that policy would
111 : : * keep them unprotected, which the policy contract forbids. */
112 : 19190 : static inline bool BlockStore_TrailerFits(const struct SolidSyslogSecurityPolicy* policy)
113 : : {
114 : 19190 : return policy->TrailerSize <= SOLIDSYSLOG_MAX_INTEGRITY_SIZE;
115 : : }
116 : :
117 : 19188 : static struct SolidSyslogStore* BlockStore_CreateInSlot(
118 : : const struct SolidSyslogBlockStoreConfig* config,
119 : : struct SolidSyslogSecurityPolicy* policy
120 : : )
121 : : {
122 : 19188 : struct SolidSyslogStore* result = SolidSyslogNullStore_Get();
123 : 19188 : size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&BlockStore_Allocator);
124 : :
125 [ + + ]: 19188 : if (SolidSyslogPoolAllocator_IndexIsValid(&BlockStore_Allocator, index))
126 : : {
127 : 19186 : struct SolidSyslogRecordStore* recordStore = SolidSyslogRecordStore_Create(policy);
128 : :
129 [ + + ]: 19186 : if (recordStore != NULL)
130 : : {
131 [ + + ]: 19185 : if (!BlockStore_DeviceCanHoldOneRecord(config, recordStore))
132 : : {
133 : : /* The device's block is smaller than one worst-case record. The store
134 : : * still works - BuildBlockSequenceConfig grows the block to the minimum so
135 : : * a record always fits - but the device was configured below a usable size,
136 : : * so surface it as a WARNING (delivered, degraded) rather than failing. */
137 : 4 : BlockStore_Report(
138 : : SOLIDSYSLOG_SEVERITY_WARNING,
139 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
140 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_BLOCK_TOO_SMALL
141 : : );
142 : : }
143 : :
144 : : struct SolidSyslogBlockSequenceConfig blockConfig =
145 : 19185 : BlockStore_BuildBlockSequenceConfig(config, recordStore);
146 : 19185 : struct SolidSyslogBlockSequence* blockSequence = SolidSyslogBlockSequence_Create(&blockConfig);
147 : :
148 [ + + ]: 19185 : if (blockSequence != NULL)
149 : : {
150 : 19183 : SolidSyslogBlockStore_Initialise(&BlockStore_Pool[index].Base, recordStore, blockSequence);
151 : 19183 : result = &BlockStore_Pool[index].Base;
152 : : }
153 : : else
154 : : {
155 : 2 : SolidSyslogRecordStore_Destroy(recordStore);
156 : 2 : (void) SolidSyslogPoolAllocator_FreeIfInUse(&BlockStore_Allocator, index, NULL, NULL);
157 : : }
158 : : }
159 : : else
160 : : {
161 : 1 : (void) SolidSyslogPoolAllocator_FreeIfInUse(&BlockStore_Allocator, index, NULL, NULL);
162 : : }
163 : : }
164 : :
165 [ + + ]: 19188 : if (result == SolidSyslogNullStore_Get())
166 : : {
167 : 5 : BlockStore_Report(
168 : : SOLIDSYSLOG_POOL_EXHAUSTED_SEVERITY,
169 : : SOLIDSYSLOG_CAT_POOL_EXHAUSTED,
170 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_POOL_EXHAUSTED
171 : : );
172 : : }
173 : :
174 : 19188 : return result;
175 : : }
176 : :
177 : 19185 : static struct SolidSyslogBlockSequenceConfig BlockStore_BuildBlockSequenceConfig(
178 : : const struct SolidSyslogBlockStoreConfig* config,
179 : : const struct SolidSyslogRecordStore* recordStore
180 : : )
181 : : {
182 : : /* The device is the single source of truth for block size, but a block smaller than
183 : : * one worst-case record is grown to that floor so a single record always fits (Create
184 : : * has already emitted a WARNING for that case). */
185 : 19185 : size_t minBlockSize = SolidSyslogRecordStore_RecordSize(recordStore, SOLIDSYSLOG_MAX_MESSAGE_SIZE);
186 : 19185 : size_t deviceBlockSize = SolidSyslogBlockDevice_GetBlockSize(config->BlockDevice);
187 : 19185 : size_t maxBlockSize = (deviceBlockSize < minBlockSize) ? minBlockSize : deviceBlockSize;
188 : :
189 : 19185 : struct SolidSyslogBlockSequenceConfig blockConfig = {
190 : 19185 : .BlockDevice = config->BlockDevice,
191 : : .MaxBlockSize = maxBlockSize,
192 : 19185 : .MaxBlocks = config->MaxBlocks,
193 : 19185 : .GetDiscardPolicy = config->GetDiscardPolicy,
194 : 19185 : .DiscardPolicyContext = config->DiscardPolicyContext,
195 : 19185 : .GetCapacityThreshold = config->GetCapacityThreshold,
196 : 19185 : .OnThresholdCrossed = config->OnThresholdCrossed,
197 : 19185 : .ThresholdContext = config->ThresholdContext,
198 : : };
199 : 19185 : return blockConfig;
200 : : }
201 : :
202 : : /* True when the device's block can hold one worst-case record (max message + the active
203 : : * policy's trailer + record framing). When false the block is grown to that floor and a
204 : : * WARNING is emitted - the store works, but the device's configured size was degraded. */
205 : 19185 : static bool BlockStore_DeviceCanHoldOneRecord(
206 : : const struct SolidSyslogBlockStoreConfig* config,
207 : : const struct SolidSyslogRecordStore* recordStore
208 : : )
209 : : {
210 : 19185 : size_t minBlockSize = SolidSyslogRecordStore_RecordSize(recordStore, SOLIDSYSLOG_MAX_MESSAGE_SIZE);
211 : 19185 : return SolidSyslogBlockDevice_GetBlockSize(config->BlockDevice) >= minBlockSize;
212 : : }
213 : :
214 : 19201 : void SolidSyslogBlockStore_Destroy(struct SolidSyslogStore* base)
215 : : {
216 : 19201 : size_t index = BlockStore_IndexFromHandle(base);
217 : 19201 : bool released = false;
218 : :
219 [ + + ]: 19201 : if (SolidSyslogPoolAllocator_IndexIsValid(&BlockStore_Allocator, index))
220 : : {
221 : : /* Pull the inner pool pointers out of the slot before FreeIfInUse runs
222 : : * the SolidSyslogBlockStore_Cleanup callback, because Cleanup overwrites the
223 : : * abstract base with the NullStore vtable but the derived RecordStore /
224 : : * BlockSequence pointers stay in the slot. After the outer FreeIfInUse
225 : : * releases the ConfigLock we destroy the inner slots - keeps each pool's
226 : : * lock acquisition sequential rather than nested. */
227 : : /* cppcheck-suppress variableScope -- read here, before FreeIfInUse, as the comment above says. */
228 : 19184 : struct SolidSyslogRecordStore* recordStore = BlockStore_Pool[index].RecordStore;
229 : : /* cppcheck-suppress variableScope -- as above. */
230 : 19184 : struct SolidSyslogBlockSequence* blockSequence = BlockStore_Pool[index].BlockSequence;
231 : :
232 : 19184 : released = SolidSyslogPoolAllocator_FreeIfInUse(&BlockStore_Allocator, index, BlockStore_CleanupAtIndex, NULL);
233 : :
234 [ + + ]: 19184 : if (released)
235 : : {
236 : 19183 : SolidSyslogBlockSequence_Destroy(blockSequence);
237 : 19183 : SolidSyslogRecordStore_Destroy(recordStore);
238 : : }
239 : : }
240 : :
241 [ + + ]: 19201 : if (!released)
242 : : {
243 : 18 : BlockStore_Report(
244 : : SOLIDSYSLOG_UNKNOWN_DESTROY_SEVERITY,
245 : : SOLIDSYSLOG_CAT_UNKNOWN_DESTROY,
246 : : SOLIDSYSLOG_BLOCK_STORE_ERROR_UNKNOWN_DESTROY
247 : : );
248 : : }
249 : 19201 : }
250 : :
251 : 19201 : static inline size_t BlockStore_IndexFromHandle(const struct SolidSyslogStore* base)
252 : : {
253 : 19201 : size_t result = SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE;
254 [ + + ]: 19218 : for (size_t poolIndex = 0U; poolIndex < SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE; poolIndex++)
255 : : {
256 [ + + ]: 19201 : if (base == &BlockStore_Pool[poolIndex].Base)
257 : : {
258 : 19184 : result = poolIndex;
259 : 19184 : break;
260 : : }
261 : : }
262 : 19201 : return result;
263 : : }
264 : :
265 : 19183 : static inline void BlockStore_CleanupAtIndex(size_t index, void* context)
266 : : {
267 : : (void) context;
268 : 19183 : SolidSyslogBlockStore_Cleanup(&BlockStore_Pool[index].Base);
269 : 19183 : }
|