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 "SolidSyslogFatFsFile.h"
6 : :
7 : : #include <stdbool.h>
8 : : #include <stddef.h>
9 : : #include <stdint.h>
10 : :
11 : : #include "SolidSyslogError.h"
12 : : #include "SolidSyslogErrorCategory.h"
13 : : #include "SolidSyslogFatFsFileErrors.h"
14 : : #include "SolidSyslogFatFsFilePrivate.h"
15 : : #include "SolidSyslogFileCategories.h"
16 : : #include "SolidSyslogFileDefinition.h"
17 : : #include "SolidSyslogNullFile.h"
18 : : #include "ff.h"
19 : :
20 : : const struct SolidSyslogErrorSource SolidSyslogFatFsFileErrorSource = {"FatFsFile", SolidSyslogFileErrors_Name};
21 : :
22 : : #define FATFSFILE_READ_WRITE_OR_CREATE (FA_READ | FA_WRITE | FA_OPEN_ALWAYS)
23 : :
24 : : static bool FatFsFile_Open(struct SolidSyslogFile* base, const char* path);
25 : : static void FatFsFile_Close(struct SolidSyslogFile* base);
26 : : static bool FatFsFile_IsOpen(struct SolidSyslogFile* base);
27 : : static bool FatFsFile_Read(struct SolidSyslogFile* base, void* buf, size_t count);
28 : : static bool FatFsFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count);
29 : : static void FatFsFile_SeekTo(struct SolidSyslogFile* base, size_t offset);
30 : : static bool FatFsFile_Size(struct SolidSyslogFile* base, size_t* size);
31 : : static void FatFsFile_Truncate(struct SolidSyslogFile* base);
32 : : static bool FatFsFile_Exists(struct SolidSyslogFile* base, const char* path);
33 : : static bool FatFsFile_Delete(struct SolidSyslogFile* base, const char* path);
34 : :
35 : : static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromBase(struct SolidSyslogFile* base);
36 : : static inline FIL* FatFsFile_Handle(struct SolidSyslogFile* base);
37 : : static inline void FatFsFile_ReportFailure(enum SolidSyslogFileErrors code, FRESULT result);
38 : : static inline bool FatFsFile_Sync(struct SolidSyslogFile* base);
39 : : static inline bool FatFsFile_IsCloseStillOwed(FRESULT result);
40 : : static bool FatFsFile_SettleOwedClose(struct SolidSyslogFatFsFile* self);
41 : : static inline bool FatFsFile_IsRemovedOrAbsent(FRESULT result);
42 : :
43 : 60 : void SolidSyslogFatFsFile_Initialise(struct SolidSyslogFile* base)
44 : : {
45 : 60 : struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base);
46 : 60 : self->Base.Open = FatFsFile_Open;
47 : 60 : self->Base.Close = FatFsFile_Close;
48 : 60 : self->Base.IsOpen = FatFsFile_IsOpen;
49 : 60 : self->Base.Read = FatFsFile_Read;
50 : 60 : self->Base.Write = FatFsFile_Write;
51 : 60 : self->Base.SeekTo = FatFsFile_SeekTo;
52 : 60 : self->Base.Size = FatFsFile_Size;
53 : 60 : self->Base.Truncate = FatFsFile_Truncate;
54 : 60 : self->Base.Exists = FatFsFile_Exists;
55 : 60 : self->Base.Delete = FatFsFile_Delete;
56 : 60 : self->IsOpen = false;
57 : 60 : self->CloseOwed = false;
58 : 60 : }
59 : :
60 : 254 : static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromBase(struct SolidSyslogFile* base)
61 : : {
62 : 254 : return (struct SolidSyslogFatFsFile*) base;
63 : : }
64 : :
65 : : /* A close still owed is tried once more; the slot is given back whatever it
66 : : * answers. */
67 : 60 : void SolidSyslogFatFsFile_Cleanup(struct SolidSyslogFile* base)
68 : : {
69 : 60 : struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base);
70 [ + + ]: 60 : if (self->IsOpen)
71 : : {
72 : 31 : FatFsFile_Close(base);
73 : : }
74 [ + + ]: 29 : else if (self->CloseOwed)
75 : : {
76 : 6 : (void) FatFsFile_SettleOwedClose(self);
77 : : }
78 : : else
79 : : {
80 : : /* closed, and nothing owed */
81 : : }
82 : 60 : self->CloseOwed = false;
83 : : /* Overwrite the abstract base with the shared NullFile vtable so
84 : : * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */
85 : 60 : *base = *SolidSyslogNullFile_Get();
86 : 60 : }
87 : :
88 : 45 : static void FatFsFile_Close(struct SolidSyslogFile* base)
89 : : {
90 : 45 : struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base);
91 [ + + ]: 45 : if (self->IsOpen)
92 : : {
93 : 43 : FRESULT result = f_close(&self->Fp);
94 : : /* Closed, as the contract says, whatever f_close answers. A failed
95 : : * sync leaves FatFs holding the file object, so the close is owed. */
96 : 43 : self->IsOpen = false;
97 : 43 : self->CloseOwed = FatFsFile_IsCloseStillOwed(result);
98 [ + + ]: 43 : if (result != FR_OK)
99 : : {
100 : 13 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_CLOSE_FAILED, result);
101 : : }
102 : : }
103 : 45 : }
104 : :
105 : : /* FR_INVALID_OBJECT is FatFs saying it no longer knows the file object - a
106 : : * remount discards every one, and its locks with them - so there is nothing
107 : : * left for a later f_close to release. */
108 : 55 : static inline bool FatFsFile_IsCloseStillOwed(FRESULT result)
109 : : {
110 [ + + + + ]: 55 : return (result != FR_OK) && (result != FR_INVALID_OBJECT);
111 : : }
112 : :
113 : : /* The fault, with the FRESULT behind it when the call answered one. A short
114 : : * transfer that FatFs answers FR_OK leaves no code. */
115 : 36 : static inline void FatFsFile_ReportFailure(enum SolidSyslogFileErrors code, FRESULT result)
116 : : {
117 [ + + ]: 36 : if (result != FR_OK)
118 : : {
119 : 34 : SolidSyslog_ErrorWithNativeCode(
120 : : SOLIDSYSLOG_FILE_IO_FAILED_SEVERITY,
121 : : &SolidSyslogFatFsFileErrorSource,
122 : : SOLIDSYSLOG_CAT_FILE_IO_FAILED,
123 : : (int32_t) code,
124 : : (int32_t) result
125 : : );
126 : : }
127 : : else
128 : : {
129 : 2 : FatFsFile_Report(SOLIDSYSLOG_FILE_IO_FAILED_SEVERITY, SOLIDSYSLOG_CAT_FILE_IO_FAILED, code);
130 : : }
131 : 36 : }
132 : :
133 : 48 : static bool FatFsFile_Open(struct SolidSyslogFile* base, const char* path)
134 : : {
135 : 48 : struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base);
136 : 48 : bool opened = false;
137 : : /* An owed close is settled before the file object is reused: f_open would
138 : : * discard what FatFs still holds in it, and its FF_FS_LOCK entry with it.
139 : : * One that cannot be settled refuses the open, and the file stays closed
140 : : * with the close still owed for the next one. */
141 [ + + ]: 48 : if (FatFsFile_SettleOwedClose(self))
142 : : {
143 : 45 : FRESULT result = f_open(&self->Fp, path, FATFSFILE_READ_WRITE_OR_CREATE);
144 : 45 : self->IsOpen = (result == FR_OK);
145 [ + + ]: 45 : if (!self->IsOpen)
146 : : {
147 : 2 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_OPEN_FAILED, result);
148 : : }
149 : 45 : opened = self->IsOpen;
150 : : }
151 : 48 : return opened;
152 : : }
153 : :
154 : : /* True when nothing is owed, or the owed close has now gone through. A retry
155 : : * that fails again is reported again. */
156 : 54 : static bool FatFsFile_SettleOwedClose(struct SolidSyslogFatFsFile* self)
157 : : {
158 [ + + ]: 54 : if (self->CloseOwed)
159 : : {
160 : 12 : FRESULT result = f_close(&self->Fp);
161 : 12 : self->CloseOwed = FatFsFile_IsCloseStillOwed(result);
162 [ + + ]: 12 : if (self->CloseOwed)
163 : : {
164 : 9 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_CLOSE_FAILED, result);
165 : : }
166 : : }
167 : 54 : return !self->CloseOwed;
168 : : }
169 : :
170 : 6 : static bool FatFsFile_IsOpen(struct SolidSyslogFile* base)
171 : : {
172 : 6 : return FatFsFile_SelfFromBase(base)->IsOpen;
173 : : }
174 : :
175 : 7 : static bool FatFsFile_Read(struct SolidSyslogFile* base, void* buf, size_t count)
176 : : {
177 : 7 : UINT bytesRead = 0U;
178 : 7 : FRESULT result = f_read(FatFsFile_Handle(base), buf, (UINT) count, &bytesRead);
179 : : /* A short read is FR_OK with fewer bytes; the store judges it. */
180 [ + + ]: 7 : if (result != FR_OK)
181 : : {
182 : 2 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_READ_FAILED, result);
183 : : }
184 [ + + + + ]: 7 : return (result == FR_OK) && (bytesRead == count);
185 : : }
186 : :
187 : 35 : static inline FIL* FatFsFile_Handle(struct SolidSyslogFile* base)
188 : : {
189 : 35 : return &FatFsFile_SelfFromBase(base)->Fp;
190 : : }
191 : :
192 : 10 : static bool FatFsFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count)
193 : : {
194 : 10 : UINT bytesWritten = 0U;
195 : 10 : FRESULT result = f_write(FatFsFile_Handle(base), buf, (UINT) count, &bytesWritten);
196 : : /* A full volume is FR_OK with fewer bytes: a failed write with no code. */
197 [ + + + + ]: 10 : bool wroteAllData = (result == FR_OK) && (bytesWritten == count);
198 [ + + ]: 10 : if (wroteAllData)
199 : : {
200 : 5 : wroteAllData = FatFsFile_Sync(base);
201 : : }
202 : : else
203 : : {
204 : 5 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_WRITE_FAILED, result);
205 : : }
206 : 10 : return wroteAllData;
207 : : }
208 : :
209 : 5 : static inline bool FatFsFile_Sync(struct SolidSyslogFile* base)
210 : : {
211 : 5 : FRESULT result = f_sync(FatFsFile_Handle(base));
212 [ + + ]: 5 : if (result != FR_OK)
213 : : {
214 : 2 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_SYNC_FAILED, result);
215 : : }
216 : 5 : return result == FR_OK;
217 : : }
218 : :
219 : 4 : static void FatFsFile_SeekTo(struct SolidSyslogFile* base, size_t offset)
220 : : {
221 : 4 : FRESULT result = f_lseek(FatFsFile_Handle(base), (FSIZE_t) offset);
222 [ + + ]: 4 : if (result != FR_OK)
223 : : {
224 : 1 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_SEEK_FAILED, result);
225 : : }
226 : 4 : }
227 : :
228 : 2 : static bool FatFsFile_Size(struct SolidSyslogFile* base, size_t* size)
229 : : {
230 : : /* f_size reads the file object's own record of its length and cannot fail. */
231 : 2 : *size = (size_t) f_size(FatFsFile_Handle(base));
232 : 2 : return true;
233 : : }
234 : :
235 : : /* f_truncate cuts the file at its current position, so the seek to the start
236 : : * is part of the truncate: a seek that fails leaves the file where it was,
237 : : * and is reported as the truncate failing. */
238 : 4 : static void FatFsFile_Truncate(struct SolidSyslogFile* base)
239 : : {
240 : 4 : FRESULT result = f_lseek(FatFsFile_Handle(base), 0);
241 [ + + ]: 4 : if (result == FR_OK)
242 : : {
243 : 3 : result = f_truncate(FatFsFile_Handle(base));
244 : : }
245 [ + + ]: 4 : if (result != FR_OK)
246 : : {
247 : 2 : FatFsFile_ReportFailure(SOLIDSYSLOG_FILE_ERROR_TRUNCATE_FAILED, result);
248 : : }
249 : 4 : }
250 : :
251 : : /* A path that is not there is the answer, not a fault, so nothing is reported. */
252 : 3 : static bool FatFsFile_Exists(struct SolidSyslogFile* base, const char* path)
253 : : {
254 : : (void) base;
255 : 3 : return f_stat(path, NULL) == FR_OK;
256 : : }
257 : :
258 : 5 : static bool FatFsFile_Delete(struct SolidSyslogFile* base, const char* path)
259 : : {
260 : : (void) base;
261 : 5 : return FatFsFile_IsRemovedOrAbsent(f_unlink(path));
262 : : }
263 : :
264 : : /* The contract counts an absent path as removed, whether the file or its
265 : : * directory is missing. */
266 : 5 : static inline bool FatFsFile_IsRemovedOrAbsent(FRESULT result)
267 : : {
268 [ + + + + : 5 : return (result == FR_OK) || (result == FR_NO_FILE) || (result == FR_NO_PATH);
+ + ]
269 : : }
|