Parent epic: #512 (E29) · Depends on: S29.02 (#517, done — FREERTOS_PLUS_FAT_PATH provisioned in both images). Independent of S29.03 (#520). Unblocks: S29.05 (media driver + PlusTCP-target swap).
Motivation
E29 needs a SolidSyslogFile implementation backed by FreeRTOS-Plus-FAT so the PlusTCP target can run a real FS-backed store (S29.05), proving the SolidSyslogFile seam is FS-vendor-portable the same way E28 proved the transport seam is TCP-vendor-portable. This story builds only the host-tested adapter pack — no media driver, no QEMU, no target wiring (all S29.05). Pure host TDD against a fake, mirroring the existing Platform/FatFs/ pack 1:1.
Note on the epic wording: the epic lists this pack as "3-TU split + Errors + Messages". S12.26 (#514) deleted all 43 *Messages.c tables — the library is codes-only now — so this pack has no Messages TU. The current Platform/FatFs/ pack (3 source files + 1 Errors header) is the correct template. (Epic body corrected to match.)
Scope — mirror Platform/FatFs/ exactly
New pack, same 3-TU split as SolidSyslogFatFsFile:
| File |
Role |
Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h |
_Create(void) / _Destroy(base) |
Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h |
enum SolidSyslogPlusFatFileErrors (PLUSFATFILE_ERROR_POOL_EXHAUSTED, _UNKNOWN_DESTROY, _MAX) + extern …PlusFatFileErrorSource |
Platform/PlusFat/Source/SolidSyslogPlusFatFile.c |
vtable + Initialise/Cleanup, the ff_* ops, PlusFatFileErrorSource = {"PlusFatFile"} |
Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.h |
struct (Base + FF_FILE* Fp + bool IsOpen) + _Report static-inline |
Platform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c |
pool + _Create/_Destroy via SolidSyslogPoolAllocator |
Platform/PlusFat/Source/.clang-tidy |
copy from FatFs |
Platform/PlusFat/CMakeLists.txt |
INTERFACE library (header-configured — the integrator's FreeRTOSFATConfig.h flows into each consumer's compile, same rationale as FatFs) |
Pool tunable reuses the role-named SOLIDSYSLOG_FILE_POOL_SIZE (one File impl links per build).
ff_* ↔ SolidSyslogFile vtable mapping (epic-locked; pin-check at TDD time)
Plus-FAT's ff_stdio is a FILE*-style handle API (FF_FILE*), unlike ChaN FatFs's caller-owned FIL, so the struct holds an FF_FILE* (NULL when closed):
| vtable op |
FatFs (today) |
Plus-FAT (this story) |
Open |
f_open(&Fp, path, FA_READ|FA_WRITE|FA_OPEN_ALWAYS) |
ff_fopen(path,"r+"), fall back to "w+" on NULL (no OPEN_ALWAYS flag) |
Close |
f_close(&Fp) |
ff_fclose(Fp) |
IsOpen |
tracked bool |
tracked bool (Fp != NULL) |
Read |
f_read |
ff_fread |
Write |
f_write + f_sync |
ff_fwrite + ff_fflush (durability after every write — same contract) |
SeekTo |
f_lseek |
ff_fseek(Fp, off, FF_SEEK_SET) |
Size |
f_size |
ff_filelength(Fp) |
Truncate |
f_lseek(0) + f_truncate |
ff_fseek(0) + ff_seteof(Fp) |
Exists |
f_stat(path, NULL) |
ff_stat(path, …) |
Delete |
f_unlink(path) |
ff_remove(path) |
Pin-check at TDD time that ff_seteof, ff_stat, ff_remove exist with these signatures in the pinned Plus-FAT SHA 8d38036 (S29.02 already pin-checked ff_seteof).
PlusFatFake + host test
Tests/Support/PlusFatFakes/ — host fake implementing the ff_* functions the adapter calls, with the same set/spy surface as FatFsFake (result injection, call counts, last-path/last-mode/last-bytes capture, programmable read source + file size). New test exe Tests/PlusFat/ (SolidSyslogPlusFatFileTest.cpp behavioural + SolidSyslogPlusFatFilePoolTest.cpp pool + main.cpp), gated on FREERTOS_PLUS_FAT_PATH in CMakeLists.txt + Tests/CMakeLists.txt. 100% host line+branch (CI gate 90%, TDD target 100%).
FreeRTOS coupling — handled honestly, not hidden
Unlike ChaN FatFs (OS-agnostic), Plus-FAT's ff_stdio.h/ff_headers.h reference FreeRTOS types (FF_IOManager uses semaphores). Plus-FAT is FreeRTOS-coupled and we surface that rather than papering over it: the host test recompiles the adapter against the real Plus-FAT headers from $FREERTOS_PLUS_FAT_PATH + FreeRtosFakes for kernel types + a test-local FreeRTOSFATConfig.h, with PlusFatFake providing the ff_* impls. (Same real-headers-first stance as FatFsFake.)
CI / build wiring
Add Platform/PlusFat/ alongside FatFs in the freertos analysis lanes: -IPlatform/PlusFat/Interface + Platform/PlusFat/Source/ in cppcheck + cppcheck-misra, and the freertos analyze-tidy / analyze-iwyu source+include lists. New SolidSyslogPlusFatFileTest runs in the freertos host lane. CLAUDE.md gets the SolidSyslogPlusFatFile.h / …Errors.h public-header rows (headers are born here; the docs/integrating-plusfat.md guide stays in S29.05).
Acceptance criteria
Out of scope
FF_Disk_t semihosting media driver, PlusTCP-target FatFs→PlusFAT swap, @store BDD on the target, docs/integrating-plusfat.md — all S29.05.
Environments
freertos host TDD (cpputest-freertos image, FREERTOS_PLUS_FAT_PATH); freertos analysis lanes (tidy/iwyu/cppcheck).
Parent epic: #512 (E29) · Depends on: S29.02 (#517, done —
FREERTOS_PLUS_FAT_PATHprovisioned in both images). Independent of S29.03 (#520). Unblocks: S29.05 (media driver + PlusTCP-target swap).Motivation
E29 needs a
SolidSyslogFileimplementation backed by FreeRTOS-Plus-FAT so the PlusTCP target can run a real FS-backed store (S29.05), proving theSolidSyslogFileseam is FS-vendor-portable the same way E28 proved the transport seam is TCP-vendor-portable. This story builds only the host-tested adapter pack — no media driver, no QEMU, no target wiring (all S29.05). Pure host TDD against a fake, mirroring the existingPlatform/FatFs/pack 1:1.Scope — mirror
Platform/FatFs/exactlyNew pack, same 3-TU split as
SolidSyslogFatFsFile:Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h_Create(void)/_Destroy(base)Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.henum SolidSyslogPlusFatFileErrors(PLUSFATFILE_ERROR_POOL_EXHAUSTED,_UNKNOWN_DESTROY,_MAX) +extern …PlusFatFileErrorSourcePlatform/PlusFat/Source/SolidSyslogPlusFatFile.cInitialise/Cleanup, theff_*ops,PlusFatFileErrorSource = {"PlusFatFile"}Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.hBase+FF_FILE* Fp+bool IsOpen) +_Reportstatic-inlinePlatform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c_Create/_DestroyviaSolidSyslogPoolAllocatorPlatform/PlusFat/Source/.clang-tidyPlatform/PlusFat/CMakeLists.txtINTERFACElibrary (header-configured — the integrator'sFreeRTOSFATConfig.hflows into each consumer's compile, same rationale as FatFs)Pool tunable reuses the role-named
SOLIDSYSLOG_FILE_POOL_SIZE(one File impl links per build).ff_*↔SolidSyslogFilevtable mapping (epic-locked; pin-check at TDD time)Plus-FAT's
ff_stdiois aFILE*-style handle API (FF_FILE*), unlike ChaN FatFs's caller-ownedFIL, so the struct holds anFF_FILE*(NULL when closed):Openf_open(&Fp, path, FA_READ|FA_WRITE|FA_OPEN_ALWAYS)ff_fopen(path,"r+"), fall back to"w+"on NULL (noOPEN_ALWAYSflag)Closef_close(&Fp)ff_fclose(Fp)IsOpenboolbool(Fp != NULL)Readf_readff_freadWritef_write+f_syncff_fwrite+ff_fflush(durability after every write — same contract)SeekTof_lseekff_fseek(Fp, off, FF_SEEK_SET)Sizef_sizeff_filelength(Fp)Truncatef_lseek(0)+f_truncateff_fseek(0)+ff_seteof(Fp)Existsf_stat(path, NULL)ff_stat(path, …)Deletef_unlink(path)ff_remove(path)Pin-check at TDD time that
ff_seteof,ff_stat,ff_removeexist with these signatures in the pinned Plus-FAT SHA8d38036(S29.02 already pin-checkedff_seteof).PlusFatFake+ host testTests/Support/PlusFatFakes/— host fake implementing theff_*functions the adapter calls, with the same set/spy surface asFatFsFake(result injection, call counts, last-path/last-mode/last-bytes capture, programmable read source + file size). New test exeTests/PlusFat/(SolidSyslogPlusFatFileTest.cppbehavioural +SolidSyslogPlusFatFilePoolTest.cpppool +main.cpp), gated onFREERTOS_PLUS_FAT_PATHinCMakeLists.txt+Tests/CMakeLists.txt. 100% host line+branch (CI gate 90%, TDD target 100%).FreeRTOS coupling — handled honestly, not hidden
Unlike ChaN FatFs (OS-agnostic), Plus-FAT's
ff_stdio.h/ff_headers.hreference FreeRTOS types (FF_IOManageruses semaphores). Plus-FAT is FreeRTOS-coupled and we surface that rather than papering over it: the host test recompiles the adapter against the real Plus-FAT headers from$FREERTOS_PLUS_FAT_PATH+FreeRtosFakesfor kernel types + a test-localFreeRTOSFATConfig.h, withPlusFatFakeproviding theff_*impls. (Same real-headers-first stance asFatFsFake.)CI / build wiring
Add
Platform/PlusFat/alongside FatFs in the freertos analysis lanes:-IPlatform/PlusFat/Interface+Platform/PlusFat/Source/in cppcheck + cppcheck-misra, and the freertosanalyze-tidy/analyze-iwyusource+include lists. NewSolidSyslogPlusFatFileTestruns in the freertos host lane. CLAUDE.md gets theSolidSyslogPlusFatFile.h/…Errors.hpublic-header rows (headers are born here; thedocs/integrating-plusfat.mdguide stays in S29.05).Acceptance criteria
Platform/PlusFat/pack exists (3-TU split + Errors header, no Messages), structurally identical toPlatform/FatFs/.SolidSyslogPlusFatFileimplements all 10SolidSyslogFilevtable ops viaff_*; write durability viaff_fflush.PlusFatFake+Tests/PlusFat/at 100% host line+branch, gated onFREERTOS_PLUS_FAT_PATH.SolidSyslogNullFile;Cleanupcloses any open handle before release.ff_seteof/ff_stat/ff_removepin-checked in SHA8d38036.Platform/PlusFat/added; no new MISRA findings vsmain.Out of scope
FF_Disk_tsemihosting media driver, PlusTCP-target FatFs→PlusFAT swap,@storeBDD on the target,docs/integrating-plusfat.md— all S29.05.Environments
freertos host TDD (cpputest-freertos image,
FREERTOS_PLUS_FAT_PATH); freertos analysis lanes (tidy/iwyu/cppcheck).