Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ jobs:
-IPlatform/PlusTcp/Interface
-IPlatform/LwipRaw/Interface
-IPlatform/FatFs/Interface
-IPlatform/PlusFat/Interface
--xml --xml-version=2
Core/Source/
Platform/Atomics/Source/
Expand All @@ -720,6 +721,7 @@ jobs:
Platform/PlusTcp/Source/
Platform/LwipRaw/Source/
Platform/FatFs/Source/
Platform/PlusFat/Source/
2> build/cppcheck/cppcheck-report.xml

- name: Upload cppcheck report
Expand Down Expand Up @@ -756,6 +758,7 @@ jobs:
-IPlatform/PlusTcp/Interface
-IPlatform/LwipRaw/Interface
-IPlatform/FatFs/Interface
-IPlatform/PlusFat/Interface
Core/Source/
Platform/Atomics/Source/
Platform/Posix/Source/
Expand All @@ -766,6 +769,7 @@ jobs:
Platform/PlusTcp/Source/
Platform/LwipRaw/Source/
Platform/FatFs/Source/
Platform/PlusFat/Source/

- name: Generate cppcheck-misra XML report
if: success() || failure()
Expand All @@ -787,6 +791,7 @@ jobs:
-IPlatform/PlusTcp/Interface \
-IPlatform/LwipRaw/Interface \
-IPlatform/FatFs/Interface \
-IPlatform/PlusFat/Interface \
--xml --xml-version=2 \
Core/Source/ \
Platform/Atomics/Source/ \
Expand All @@ -798,6 +803,7 @@ jobs:
Platform/PlusTcp/Source/ \
Platform/LwipRaw/Source/ \
Platform/FatFs/Source/ \
Platform/PlusFat/Source/ \
2> build/cppcheck-misra/cppcheck-misra-report.xml

- name: Upload cppcheck-misra report
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*`
| `SolidSyslogWindowsFileErrors.h` | Any code installing an error handler that wants to react to WindowsFile-specific events (pointer-identity match on `WindowsFileErrorSource`, switch on `enum SolidSyslogWindowsFileErrors`) | `enum SolidSyslogWindowsFileErrors` (`WINDOWSFILE_ERROR_*` codes + `WINDOWSFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource WindowsFileErrorSource`. Integrators ignore if not handling errors per source. |
| `SolidSyslogFatFsFile.h` | System setup code on targets using ChaN FatFs as the file layer (bare-metal flash / SD / eMMC, FreeRTOS, Zephyr, NuttX, …) | `SolidSyslogFatFsFile_Create(void)`, `_Destroy(file)` (wraps `f_open` / `f_close` / `f_read` / `f_write` with `f_sync` after every successful write so a power loss never loses a record the BlockStore claimed it stored; integrator supplies `diskio.c` and — if `FF_FS_REENTRANT=1` — `ffsystem.c`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. |
| `SolidSyslogFatFsFileErrors.h` | Any code installing an error handler that wants to react to FatFsFile-specific events (pointer-identity match on `FatFsFileErrorSource`, switch on `enum SolidSyslogFatFsFileErrors`) | `enum SolidSyslogFatFsFileErrors` (`FATFSFILE_ERROR_*` codes + `FATFSFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource FatFsFileErrorSource`. Integrators ignore if not handling errors per source. |
| `SolidSyslogPlusFatFile.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-FAT as the file layer | `SolidSyslogPlusFatFile_Create(void)`, `_Destroy(file)` (wraps `ff_fopen` (`"r+"` then `"w+"` fallback — open-or-create without truncation) / `ff_fclose` / `ff_fread` / `ff_fwrite` with `ff_fflush` after every complete write so a power loss never loses a record the BlockStore claimed it stored; `ff_fseek` / `ff_seteof` for truncate-to-empty; `ff_filelength`, `ff_stat`, `ff_remove`). The open-state is the `FF_FILE*` sentinel (no separate flag). Plus-FAT is FreeRTOS-coupled (`ff_headers.h` pulls `FreeRTOS.h` / `task.h` / `semphr.h`); integrator supplies the `FF_Disk_t` media driver and `FreeRTOSFATConfig.h`. Instance struct lives in a library-internal static pool (E11; role tunable `SOLIDSYSLOG_FILE_POOL_SIZE`). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. |
| `SolidSyslogPlusFatFileErrors.h` | Any code installing an error handler that wants to react to PlusFatFile-specific events (pointer-identity match on `PlusFatFileErrorSource`, switch on `enum SolidSyslogPlusFatFileErrors`) | `enum SolidSyslogPlusFatFileErrors` (`PLUSFATFILE_ERROR_*` codes + `PLUSFATFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource PlusFatFileErrorSource`. Integrators ignore if not handling errors per source. |
| `SolidSyslogPosixClock.h` | System setup code using POSIX clock | `SolidSyslogPosixClock_GetTimestamp` |
| `SolidSyslogPosixHostname.h` | String callback implementor using POSIX hostname | `SolidSyslogPosixHostname_Get` (writes into `SolidSyslogFormatter*`) |
| `SolidSyslogPosixProcessId.h` | String callback implementor using POSIX process ID | `SolidSyslogPosixProcessId_Get` (writes into `SolidSyslogFormatter*`) |
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,13 @@ if(DEFINED ENV{FATFS_PATH})
add_subdirectory(Platform/FatFs)
endif()

# FreeRTOS-Plus-FAT adapters ship as sources via an INTERFACE library too — the
# integrator's FreeRTOSFATConfig.h flows into each consumer's compile. Gated on
# the upstream Plus-FAT sources being present in the build environment.
if(DEFINED ENV{FREERTOS_PLUS_FAT_PATH})
add_subdirectory(Platform/PlusFat)
endif()

# The cross-compiled FreeRTOS target is selected by the arm-none-eabi
# toolchain file (see Bdd/Targets/FreeRtos/cmake/arm-none-eabi.cmake); it
# only enters the build when a freertos-cross preset is active. The chosen
Expand Down
46 changes: 46 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# Dev Log

## 2026-06-03 — S29.04 Platform/PlusFat adapter pack (host TDD)

The FreeRTOS-Plus-FAT `SolidSyslogFile` adapter, mirroring the FatFs sibling but
adapted to the `ff_stdio` API. No media driver, no QEMU, no target swap — that is
S29.05; this story is the host-tested pack only.

### Harness spike first

Plus-FAT is FreeRTOS-coupled (unlike OS-agnostic ChaN FatFs): `ff_stdio.h` →
`ff_headers.h` pulls `FreeRTOS.h` / `task.h` / `semphr.h`. A throwaway spike proved
a host TU compiles `ff_stdio.h` given (1) a test-local `FreeRTOSFATConfig.h`
(byte-order, CWD TLS index, `portINLINE` fallback — matching Plus-FAT's own
DefaultConf), (2) `configNUM_THREAD_LOCAL_STORAGE_POINTERS >= 3` in the shared
`FreeRtosFakes/FreeRTOSConfig.h` (Plus-FAT keeps errno/CWD/ff_error in TLS slots),
and (3) the kernel + Plus-FAT include dirs. We surface that coupling in the test
include path rather than hiding it.

### Pool experience copied, behaviour TDD'd

The pool plumbing (`Static.c`) and its 9-test suite are **copied + renamed
verbatim** from FatFs — proven, agreed coverage, not re-derived. Everything
`ff_*` was driven by genuine red→green: Open (`"r+"`→`"w+"` fallback,
open-or-create without truncation), Close (guarded `ff_fclose`), Read
(`ff_fread`, item-count == requested), Write + per-write `ff_fflush` durability,
SeekTo/Size, Truncate (`ff_fseek(0)`+`ff_seteof`), Exists/Delete. 30 tests, 100%
line+branch by construction.

### Decisions

- **`FF_FILE*` sentinel is the open-state** — no separate `IsOpen` bool (FatFs
needs one because its `FIL` is embedded; Plus-FAT's handle is a pointer).
- **Adapter calls `ff_fread`/`ff_fwrite` with `xSize == 1`** so the returned item
count is the byte count; a short read/write is the single failure mode.
- **D.002 11.3 suppression** added for the one `SelfFromBase` downcast, mirroring
every other vtable class.

### Lesson — TDD, not TDD-costume

First cut wrote the whole pool allocator to satisfy a trivial `CreateSucceeds`,
then dressed it as a slice. Reverted to the literal minimum; the rule for these
pool classes is copy+rename the proven pool block (plumbing **and** its tests)
for instant agreed coverage, then do real per-op TDD for the rest. Also caught two
invalid reds that passed for free through the `NullFile` fallback
(`IsOpen`-false, `Write`-returns-true) — the genuine driver is asserting the call
reached the fake.

## 2026-06-03 — S29.03 Extract the shared FreeRTOS BDD-target pipeline

The DRY pass S29.01 deferred. The two FreeRTOS target `main.c` files
Expand Down
25 changes: 25 additions & 0 deletions Platform/PlusFat/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# FreeRTOS-Plus-FAT adapter pack — shipped as sources, not a precompiled library.
#
# Plus-FAT is header-configured: the integrator's FreeRTOSFATConfig.h flows
# through upstream headers and changes the size/layout of the FF_FILE /
# FF_IOManager objects and the visible API surface. Plus-FAT is also FreeRTOS-
# coupled (ff_headers.h pulls FreeRTOS.h / task.h / semphr.h), so compiling our
# adapter once into libSolidSyslog.a would silently break consumers that use a
# different FreeRTOSFATConfig.h or kernel config.
#
# Instead, this is an INTERFACE library — each consumer (Bdd/Targets/FreeRtos,
# Tests/PlusFat/*Test, downstream integrator apps) recompiles the adapter
# sources with its own FreeRTOSFATConfig.h on the include path. Mirrors the
# Platform/FatFs/ and Platform/FreeRtos/ packs — see
# project_header_configured_platforms memory.

add_library(SolidSyslogPlusFat INTERFACE)

target_include_directories(SolidSyslogPlusFat INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/Interface
)

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Interface/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
15 changes: 15 additions & 0 deletions Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef SOLIDSYSLOGPLUSFATFILE_H
#define SOLIDSYSLOGPLUSFATFILE_H

#include "ExternC.h"

struct SolidSyslogFile;

EXTERN_C_BEGIN

struct SolidSyslogFile* SolidSyslogPlusFatFile_Create(void);
void SolidSyslogPlusFatFile_Destroy(struct SolidSyslogFile * base);

EXTERN_C_END

#endif /* SOLIDSYSLOGPLUSFATFILE_H */
21 changes: 21 additions & 0 deletions Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef SOLIDSYSLOGPLUSFATFILEERRORS_H
#define SOLIDSYSLOGPLUSFATFILEERRORS_H

#include "ExternC.h"

EXTERN_C_BEGIN

struct SolidSyslogErrorSource;

enum SolidSyslogPlusFatFileErrors
{
PLUSFATFILE_ERROR_POOL_EXHAUSTED,
PLUSFATFILE_ERROR_UNKNOWN_DESTROY,
PLUSFATFILE_ERROR_MAX
};

extern const struct SolidSyslogErrorSource PlusFatFileErrorSource;

EXTERN_C_END

#endif /* SOLIDSYSLOGPLUSFATFILEERRORS_H */
133 changes: 133 additions & 0 deletions Platform/PlusFat/Source/SolidSyslogPlusFatFile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "SolidSyslogPlusFatFile.h"

#include <stdbool.h>
#include <stddef.h>

#include "SolidSyslogFileDefinition.h"
#include "SolidSyslogNullFile.h"
#include "SolidSyslogPlusFatFileErrors.h"
#include "SolidSyslogPlusFatFilePrivate.h"
#include "ff_stdio.h"

const struct SolidSyslogErrorSource PlusFatFileErrorSource = {"PlusFatFile"};

static bool PlusFatFile_Open(struct SolidSyslogFile* base, const char* path);
static void PlusFatFile_Close(struct SolidSyslogFile* base);
static bool PlusFatFile_IsOpen(struct SolidSyslogFile* base);
static bool PlusFatFile_Read(struct SolidSyslogFile* base, void* buf, size_t count);
static bool PlusFatFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count);
static void PlusFatFile_SeekTo(struct SolidSyslogFile* base, size_t offset);
static size_t PlusFatFile_Size(struct SolidSyslogFile* base);
static void PlusFatFile_Truncate(struct SolidSyslogFile* base);
static bool PlusFatFile_Exists(struct SolidSyslogFile* base, const char* path);
static bool PlusFatFile_Delete(struct SolidSyslogFile* base, const char* path);

static inline struct SolidSyslogPlusFatFile* PlusFatFile_SelfFromBase(struct SolidSyslogFile* base);

void PlusFatFile_Initialise(struct SolidSyslogFile* base)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
self->Base.Open = PlusFatFile_Open;
self->Base.Close = PlusFatFile_Close;
self->Base.IsOpen = PlusFatFile_IsOpen;
self->Base.Read = PlusFatFile_Read;
self->Base.Write = PlusFatFile_Write;
self->Base.SeekTo = PlusFatFile_SeekTo;
self->Base.Size = PlusFatFile_Size;
self->Base.Truncate = PlusFatFile_Truncate;
self->Base.Exists = PlusFatFile_Exists;
self->Base.Delete = PlusFatFile_Delete;
self->Fp = NULL;
}

static inline struct SolidSyslogPlusFatFile* PlusFatFile_SelfFromBase(struct SolidSyslogFile* base)
{
return (struct SolidSyslogPlusFatFile*) base;
}

void PlusFatFile_Cleanup(struct SolidSyslogFile* base)
{
PlusFatFile_Close(base);
/* Overwrite the abstract base with the shared NullFile vtable so
* use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */
*base = *SolidSyslogNullFile_Get();
}

static bool PlusFatFile_Open(struct SolidSyslogFile* base, const char* path)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
/* "r+" opens an existing file without truncating; if it does not exist,
* "w+" creates it. Together this is open-or-create with no data loss —
* Plus-FAT has no single open-always mode. */
self->Fp = ff_fopen(path, "r+");
if (self->Fp == NULL)
{
self->Fp = ff_fopen(path, "w+");
}
return self->Fp != NULL;
}

static void PlusFatFile_Close(struct SolidSyslogFile* base)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
if (self->Fp != NULL)
{
ff_fclose(self->Fp);
self->Fp = NULL;
}
}

static bool PlusFatFile_IsOpen(struct SolidSyslogFile* base)
{
return PlusFatFile_SelfFromBase(base)->Fp != NULL;
}

static bool PlusFatFile_Read(struct SolidSyslogFile* base, void* buf, size_t count)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
/* xSize == 1 so the ff_fread return value is the byte count delivered. */
return ff_fread(buf, 1, count, self->Fp) == count;
}

static bool PlusFatFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
/* Flush after every complete write so a power loss never loses a record
* the BlockStore was told had been stored. */
bool wroteAll = ff_fwrite(buf, 1, count, self->Fp) == count;
return wroteAll && (ff_fflush(self->Fp) == 0);
}

static void PlusFatFile_SeekTo(struct SolidSyslogFile* base, size_t offset)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
ff_fseek(self->Fp, (long) offset, SEEK_SET);
}

static size_t PlusFatFile_Size(struct SolidSyslogFile* base)
{
return ff_filelength(PlusFatFile_SelfFromBase(base)->Fp);
}

static void PlusFatFile_Truncate(struct SolidSyslogFile* base)
{
struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base);
/* Empty the file: rewind to the start, then set EOF there so the length
* becomes zero. ff_seteof truncates at the current position and Plus-FAT
* has no truncate-to-zero call of its own. */
ff_fseek(self->Fp, 0, SEEK_SET);
ff_seteof(self->Fp);
}

static bool PlusFatFile_Exists(struct SolidSyslogFile* base, const char* path)
{
(void) base;
FF_Stat_t status;
return ff_stat(path, &status) == 0;
}

static bool PlusFatFile_Delete(struct SolidSyslogFile* base, const char* path)
{
(void) base;
return ff_remove(path) == 0;
}
30 changes: 30 additions & 0 deletions Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef SOLIDSYSLOGPLUSFATFILEPRIVATE_H
#define SOLIDSYSLOGPLUSFATFILEPRIVATE_H

#include <stdint.h>

#include "SolidSyslogError.h"
#include "SolidSyslogFileDefinition.h"
#include "SolidSyslogPlusFatFileErrors.h"
#include "SolidSyslogPrival.h"
#include "ff_stdio.h"

struct SolidSyslogPlusFatFile
{
struct SolidSyslogFile Base;
FF_FILE* Fp;
};

void PlusFatFile_Initialise(struct SolidSyslogFile* base);
void PlusFatFile_Cleanup(struct SolidSyslogFile* base);

static inline void PlusFatFile_Report(
enum SolidSyslogSeverity severity,
uint16_t category,
enum SolidSyslogPlusFatFileErrors code
)
{
SolidSyslog_Error(severity, &PlusFatFileErrorSource, category, (int32_t) code);
}

#endif /* SOLIDSYSLOGPLUSFATFILEPRIVATE_H */
Loading
Loading