Skip to content

Commit e35a993

Browse files
committed
fix: preserve ride history I/O failures
1 parent ee11404 commit e35a993

7 files changed

Lines changed: 272 additions & 56 deletions

File tree

‎ROADMAP.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Phase 1 software progress is also underway:
6969
- [x] Notes loads complete files up to 65,535 bytes, replacing a silent 2,047-byte prefix. Recovery/read/close errors, oversized files and NUL data disable Save; byte-length and SD checks run before storage changes. Its launcher leave hook clears editor ownership. Four preceding-source controls reproduce full-note truncation; 36 native file/fault cases and five real LVGL cases pass, including maximum ASCII/UTF-8 notes and 25 reopens without retained resources in the unchanged 96 KiB pool. Linux and Windows CI retain separate source-bound evidence. Physical SD/panel and power cuts remain open.
7070
- [x] Notes confirms directory/TMP preparation before opening a writer and preserves each load/write failure through cleanup. Missing errno uses EIO; a failed open cannot become an empty editable note, and a stream-flagged positive write cannot publish. All 63 native file/fault cases and 12 real LVGL cases pass; the preceding callbacks fail 18 and six controls respectively. Exact final/backup/TMP bytes, blocked stale Save events, accepted existing-directory/missing-TMP responses and 25 failure/retry pairs are checked with closed resources and unchanged LVGL allocation counts. Logical directory/mount responses, faults and PSRAM APIs are controlled. CI retains source hashes and logs; physical SD/panel and power cuts remain open.
7171
- [x] Ride history reports recovery/open/read/close errors as unavailable instead of showing zero or partial totals. Summary appends check SD availability and preparation errors, reject failed read chunks before copying, and retain the first error through cleanup. All 50 native file/fault cases pass; the preceding callbacks fail 24 controls. Exact final/backup/temporary bytes and 25 reopens are checked with closed modeled handles. Linux CI retains source hashes and logs, and the unflashed firmware builds with unchanged configuration. Physical SD/panel and power cuts remain open.
72+
- [x] Ride history and summary writers now isolate errno at every I/O boundary. Failed opens cannot become empty history or replace an existing index; flagged positive-count reads/writes stop before later parsing, writes or publication, preserving the first cause through cleanup. All 95 history and 41 recording cases pass; the preceding history callbacks fail 32 controls and the Stop flow fails one additional missing-errno source-open control. Exact generations, retained TMPs, four 25-pair failure/retry paths and successful reloads are checked with closed resources. A published ride remains saved when its index update fails, and the failure is reported separately. Logical directory/mount responses, UI, BLE data, time and faults are controlled. The unflashed firmware builds; physical SD/panel and power cuts remain open.
7273
- [x] Ride Start checks both directories, stops on noncollision creation errors and preserves their first cause. Rejected headers/rows, periodic sync failures and known SD loss retain the named TMP and clear recording ownership without retrying publication. All 39 native recording/history/storage cases pass; the preceding source fails 27 controls. Exact CSV/index/backup bytes, 100-name limits, tick wrap and 25 same-second restart pairs are checked with closed streams/descriptors. Linux CI retains source hashes and logs. Physical SD, BLE, panel and power cuts remain open.
7374
- [x] Ring logging commits its saved timestamp only after tail recovery and a complete, successfully closed read. Failed loads retain queued samples and defer history requests until a successful retry; known SD unavailability also blocks a cached checkpoint. Appends check directory errors and preserve the first seek/tell/header/row error through close and repair. All 59 native file/queue/fault cases pass; the preceding callbacks fail 32 controls. Checks verify exact bytes, retry/deduplication, timeout handling while blocked and 25 checkpoint reopens with closed modeled handles. Linux CI retains source hashes and logs. Physical SD, BLE, panel and power cuts remain open.
7475
- [x] Ebook page reads publish their text and byte positions only after read/close success. Failed navigation retains the previous page for retry, including zero-byte and full-count read errors. Initial navigation is disabled until a page loads; callbacks reject disabled/offscreen actions and invalid positions. All 44 native file/state/fault checks pass, including 25 navigation cycles with one reused buffer and closed handles. Thirty-one preceding-callback controls fail; three new unsafe-boundary guards are tested only with the new callbacks. Linux CI retains source hashes, initialization source and logs. Physical SD/panel and power behavior remain open.

‎docs/architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ HTTP and MQTT metadata appenders capture fresh errors for preparation, file posi
9191

9292
Notes uses a temporary PSRAM buffer to load an entire file of up to 65,535 bytes and frees that buffer after LVGL copies the text. It applies the editor's character limit after loading, using LVGL's bulk text path. Recovery/open/read/close failures, oversized files and NUL data disable editing and Save. Fresh errno at each load boundary prevents a failed open from becoming an empty draft; read/probe errors survive close. The save callback rechecks ownership, SD availability and byte length, confirms directory creation and old-TMP removal before opening the writer, and discards rejected or stream-flagged writes without retrying publication. It preserves the first cause through cleanup and keeps the draft available for retry. The launcher leave hook clears its editor pointer and permission before content deletion. The existing replacement helper syncs/closes accepted `NOTE.TMP` writes, retains the previous complete generation in `NOTE.BAK`, and reports publication failures. Real LVGL checks use the unchanged 96 KiB pool; physical SD and panel behavior remain open.
9393

94-
Ride history shows totals only after recovery, reading and closing the summary succeed; a missing index is valid empty history. Storage errors display an unavailable message and update the shared SD error. Appending checks known SD availability, directory creation and temporary-file removal before copying. A failed read chunk is rejected before writing, and the first read/write/open error survives source/destination close and temporary cleanup failures. The existing replacement helper still owns sync, backup, publication and rollback. Native tests verify exact file bytes and closed modeled handles; physical FAT/SD and panel behavior remain open.
94+
Ride history shows totals only after recovery, reading and closing the summary succeed; only a confirmed missing index is valid empty history. Fresh errno at every I/O boundary prevents a failed open from becoming empty history or replacing an existing index. A flagged history line stops further reads before parsing. Storage errors display an unavailable message and update the shared SD error. Appending checks known SD availability, directory creation and temporary-file removal before copying. Failed read chunks and rejected or stream-flagged output stop before later writes or publication; the first cause survives source/destination close and temporary cleanup failures. The existing replacement helper owns sync, backup, publication and rollback. Stop keeps a successfully published ride authoritative when updating the index fails, reports that failure and reloads the original totals. Native tests verify exact file bytes and closed modeled handles; physical FAT/SD and panel behavior remain open.
9595

9696
Ride recording checks both directories and advances through its 100 same-second names only on CSV/TMP collisions. Other creation errors stop Start; a failed stream conversion closes its descriptor and attempts to remove the newly created empty TMP. Rejected headers/rows and periodic sync failures use one abort path that closes recording ownership, retains the TMP and reports the captured first cause. Tick and Stop also abort on known SD loss before another row or publication. Healthy Stop commits the CSV before rebuilding history, so an index failure leaves the published ride authoritative. Native tests compile the actual lifecycle and history helpers together with storage; UI, BLE data, time and SD boundaries are controlled.
9797

‎docs/data-formats.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Ride summary header:
6969
start_unix,duration_s,distance_km,work_kj,avg_power_w,max_power_w,avg_hr,max_hr
7070
```
7171

72-
History totals require successful backup recovery, open, read and close. A missing index shows empty history; storage errors show `History unavailable` instead of zero or partial totals. Summary appends check SD availability and preparation errors, reject failed read chunks before copying, and preserve the first error through cleanup. Failed cleanup can leave an unpublished `SUMMARY.TMP`; publication or rollback errors can also retain the temporary or backup generation. The individual ride CSV remains authoritative. Native files and controlled errors verify these boundaries; physical SD and power-loss behavior remain hardware checks.
72+
History totals require successful backup recovery, open, read and close. Only a confirmed missing index shows empty history; storage errors show `History unavailable` instead of zero or partial totals. Each operation reads its own errno, using EIO when a failure supplies no cause. A stream-flagged history line stops further reads before parsing. Summary appends check SD availability and preparation errors, reject failed read chunks before copying, and stop on rejected or stream-flagged copies, newlines, headers and rows before later writes or publication. The first error survives source/destination close and removal. Failed cleanup can leave an unpublished partial or complete-looking `SUMMARY.TMP`; publication or rollback errors can also retain the temporary or backup generation. The individual ride CSV remains authoritative, and Stop reports an index-update failure separately while reloading the original history. Native files and controlled errors verify these boundaries; physical SD and power-loss behavior remain hardware checks.
7373

7474
Heart-rate header:
7575

‎docs/testing.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,25 +151,25 @@ Generated callbacks/config/storage source, full main/function/storage/header/tes
151151
python tools/test_ride_history.py --compiler 'C:/path/to/clang.exe'
152152
```
153153

154-
This portable fixture compiles the two actual ride-history callbacks extracted from `main/main.c` and the complete `storage_io.c`. Its 50 cases check missing/empty indexes, exact aggregate totals and appended rows, backup recovery, unavailable SD, preparation/open/read/write/flush/sync/close/publication/rollback errors and 25 history reopens. Every case independently checks final, backup and temporary bytes with no remaining modeled file handles. UI and I/O error boundaries are controlled; the files use the native filesystem.
154+
This portable fixture compiles the two actual ride-history callbacks extracted from `main/main.c` and the complete `storage_io.c`. Its 95 cases check missing/empty indexes, exact aggregate totals and appended rows, backup recovery, unavailable SD, preparation/open/read/write/flush/sync/close/publication/rollback errors and 25 history reopens. Every case independently checks final, backup and temporary bytes with no remaining modeled file handles. UI, logical directory responses and I/O error boundaries are controlled; the files use the native filesystem.
155155

156-
The preceding callbacks fail 24 controls, including misleading totals after recovery/read/close errors, ignored SD/preparation errors and lost first errors during cleanup. Compounded read/copy/open failures retain their original error when later closes or removal fail. Missing read/write errno falls back to `EIO`; a successful earlier history read cannot supply a stale errno for a later failure. Multi-block and full-count read-error cases reject the failed chunk before copying. Cleanup failure can retain an unpublished partial temporary file, and publication/rollback failures can retain recovery generations. These checks do not validate every CSV field or prove physical FAT/SD latency, removal, power cuts or panel rendering.
156+
The original callback fix reproduced 24 failures, including misleading totals after recovery/read/close errors, ignored SD/preparation errors and lost first errors during cleanup. The latest boundary controls reproduce 32 failures against its successor: stale ENOENT can make a failed open look like an empty index, missing preparation/write/close errno can inherit another cause, and positive-count stream errors can allow later row writes or lose their first cause. Each operation now checks its own errno, using `EIO` when a failure supplies none. A flagged history line stops further reads before parsing; flagged copies, newlines, headers and rows stop publication before subsequent writes or explicit flush/sync. Compounded failures preserve the first cause even when close/removal fail and leave an unpublished partial or complete-looking TMP when removal fails. Twenty-five failed-open/reopen, flagged-line/reopen, preparation/retry and flagged-copy/retry pairs verify successful recovery and exact totals with closed handles. Multi-block and full-count read-error controls remain covered. These checks do not validate every CSV field or prove physical FAT/SD latency, removal, power cuts or panel rendering.
157157

158-
Generated callbacks/storage source, full main/function/storage/header/test hashes and compile/result logs stay under `build/ride-history/`. Linux CI runs the fixture and retains these files as `ride-history-logs`, including diagnostics when compilation or execution fails. Its 50 cases are separate from the Windows native total.
158+
Generated callbacks/storage source, full main/function/storage/header/test hashes and compile/result logs stay under `build/ride-history/`. Linux CI runs the fixture and retains these files as `ride-history-logs`, including diagnostics when compilation or execution fails. Its 95 cases are separate from the Windows native total.
159159

160160
## Ride recording
161161

162162
```powershell
163163
python tools/test_ride_recording.py --compiler 'C:/path/to/clang.exe'
164164
```
165165

166-
This portable fixture compiles eight actual recording, button, tick and history functions from `main/main.c` with the complete `storage_io.c`. Its 39 cases use native files, directories and descriptors to check exact ride CSV, summary and backup bytes, creation errors, header/row failures, periodic flush/sync, Stop failures, known SD loss, mixed collisions, all 100 occupied names, tick wrap, admission gates and restart after a rejected header. The 25-pair recovery case preserves both generations within each pair and checks closed streams and descriptors before removing owned files between pairs.
166+
This portable fixture compiles eight actual recording, button, tick and history functions from `main/main.c` with the complete `storage_io.c`. Its 41 cases use native files, directories and descriptors to check exact ride CSV, summary and backup bytes, creation errors, header/row failures, periodic flush/sync, Stop failures, known SD loss, mixed collisions, all 100 occupied names, tick wrap, admission gates and restart after a rejected header. The 25-pair recovery case preserves both generations within each pair and checks closed streams and descriptors before removing owned files between pairs.
167167

168-
The preceding source fails 27 controls: ignored root-directory errors, stale or missing error causes, retry after a stream-creation failure, deleted partial headers and continued recording/publication after known SD loss. Failed writes retain a named TMP and preserve their first cause through close and SD error reporting. A later Stop cannot publish an aborted recording. A successful CSV remains authoritative when updating its history index fails; that failure is reported separately.
168+
The original recording fix reproduced 27 failures: ignored root-directory errors, stale or missing error causes, retry after a stream-creation failure, deleted partial headers and continued recording/publication after known SD loss. Failed writes retain a named TMP and preserve their first cause through close and SD error reporting. A later Stop cannot publish an aborted recording. Two further controls fail the summary-source open after the ride CSV is published, with a known or missing errno. The preceding history helper fails the missing-errno control by replacing the index and reporting a safe save. Both now preserve the completed ride and original index, report the index failure once and reload the original totals with closed resources.
169169

170170
UI labels, BLE sensor caches, clocks/ticks, logical paths, SD admission/error reporting and I/O faults are controlled. The fixture seeds the ride root and an existing summary before each case; the real Start creates the date directory on its first successful run. UTC fixes filenames, and Windows descriptors use binary mode for SDK LF bytes. These checks do not execute BLE, real LVGL, SDK mount/error filtering, physical FAT/SD, the panel or power cuts. A retained TMP can contain partial or complete-looking bytes without confirmed physical durability.
171171

172-
Full main/function/storage/header/test hashes, generated function/config/storage source and compile/result logs stay under `build/ride-recording/`. Linux CI retains `ride-recording-logs`, including available failure diagnostics. These 39 cases are separate from the Windows native total and the 50 ride-history cases.
172+
Full main/function/storage/header/test hashes, generated function/config/storage source and compile/result logs stay under `build/ride-recording/`. Linux CI retains `ride-recording-logs`, including available failure diagnostics. These 41 cases are separate from the Windows native total and the 95 ride-history cases.
173173

174174
## Ring history storage
175175

0 commit comments

Comments
 (0)