Skip to content

Commit ddca2fb

Browse files
committed
fix: report incomplete Files directory listings
1 parent a6e86f2 commit ddca2fb

8 files changed

Lines changed: 426 additions & 4 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ jobs:
4141
run: python3 tools/test_ebook_pages.py --compiler cc
4242
- name: Test Files text previews
4343
run: python3 tools/test_file_preview.py --compiler cc
44+
- name: Test Files directories
45+
run: python3 tools/test_files_directory.py --compiler cc
4446
- name: Test capture reader
4547
run: |
4648
cc -std=c11 -Wall -Wextra -Werror -pedantic -I main main/capture_data.c tests/capture_data_test.c -o "$RUNNER_TEMP/capture_data_test"
@@ -196,6 +198,20 @@ jobs:
196198
if-no-files-found: warn
197199
retention-days: 14
198200

201+
- name: Retain Files directory evidence
202+
uses: actions/upload-artifact@v4
203+
if: always()
204+
with:
205+
name: files-directory-logs
206+
path: |
207+
build/files-directory/compile.log
208+
build/files-directory/result.log
209+
build/files-directory/source.json
210+
build/files-directory/files_directory.inc
211+
build/files-directory/files_directory_config.inc
212+
if-no-files-found: warn
213+
retention-days: 14
214+
199215
firmware:
200216
runs-on: ubuntu-latest
201217
container: espressif/idf:v5.4.2

‎ROADMAP.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Phase 1 software progress is also underway:
6868
- [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.
6969
- [x] Recognized Scope/I2C and saved serial CSV loads require a successful close before showing a graph or row-copy controls. Failed or empty capture loads release their row buffer immediately. All 36 real LVGL/native-file checks pass; the preceding viewers fail 15 controls. Checks cover both row limits, I2C gaps, read/seek/close and allocation faults, unchanged file/clipboard bytes and 25 success/error/reopen cycles per viewer with stable memory in the unchanged 96 KiB pool. Existing serial-copy/inspector checks pass. Windows CI retains complete viewer/parser sources and logs; physical SD/panel behavior remains open.
7070
- [x] Files text previews require a successful read and close before showing their existing 4,095-byte prefix. Failed operations show the first error, using EIO when errno is absent; SD-path failures reach the existing error callback. All 36 native file/fault cases pass with unchanged bytes and closed handles, including read/close errors, retry, routing and 25 cycles. The preceding callback fails 29 controls: 23 reproduce ignored read/close failures or retry cycles, and six concern open-error details/reporting. UI, CSV probes and logical mount paths are controlled; Linux CI retains source hashes and logs. Physical storage/panel checks remain open.
71+
- [x] Files directory browsing reports open/read/close errors, marks interrupted listings incomplete, and discloses its entry cap and skipped long paths. Fresh errno checks distinguish normal EOF from read errors and preserve the first failure through close. All 31 native directory/file cases pass with unchanged bytes, valid callback path ownership and closed handles, including 25 success/error/reopen cycles. The previous callback fails 21 controls: 14 concern silent read/close errors or recovery, three disclose limits and four add open-error details/reporting. UI, mount paths and faults are controlled; Linux CI retains source hashes and logs. SDK mounts, physical SD and panel checks remain open.
7172
- [x] Shared CSV tail recovery now scans backwards in 512-byte blocks. Eight previous-source controls exceed stdio seek budgets; a 64 KiB partial tail drops from 65,538 seeks to 130 with the same retained prefix. All 32 native byte/boundary/fault cases, original storage checks and five MQTT/HTTP logging cases pass with closed modeled handles. The unflashed firmware builds with unchanged configuration. Linux CI retains source hashes and compilation/result logs; physical SD timing and power cuts remain open.
7273
- [x] Built-in SD paths, timestamps, units, 8.3-safe collision names, `.TMP`, and `.BAK` behavior are documented.
7374
- [x] Persisted brightness choices, a dimmed two-minute screen, and configurable 5/10/30-minute or Never screen-off behavior are implemented.

‎docs/architecture.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ The Scope/I2C capture and saved serial CSV viewers require a successful close be
9595

9696
Files dispatches supported CSVs to those viewers before its text fallback. The fallback reads at most 4,095 bytes into its existing static buffer and publishes them only after checking the stream and closing successfully. Failed open/read/close operations display the first error; missing errno becomes EIO. Error reporting to the shared SD callback is scoped to the exact `/sdcard/` prefix. Native files and controlled UI/probe/path boundaries check this callback; physical storage and panel behavior remain open.
9797

98+
Files directory browsing resets errno before each directory read so per-entry `stat` errors cannot masquerade as an enumeration error at EOF. A failed read or close keeps the visited entries visible with an explicit incomplete-list warning, preserving the first error. Entry capacity and skipped long paths are disclosed. The existing 64 path slots include a parent-navigation slot when needed, and each path remains bounded to 256 bytes. SD root and descendant errors reach the shared callback. Native directory/file checks verify these transitions and callback path ownership; UI, mount paths and error injection are controlled.
99+
98100
See [Storage and compatibility contract](compatibility.md) and [Data formats](data-formats.md).
99101

100102
## Network and radio

‎docs/data-formats.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ After a supported capture or serial header is recognized, seek/read/close failur
3333

3434
The fallback text preview displays up to the first 4,095 source bytes after a successful read and close. A failed open/read/close displays the first error instead of an unconfirmed prefix, including zero-byte or full-count reads with the stream error flag set. An error-free empty read remains an empty preview. Paths beginning `/sdcard/` forward failures to the existing SD error callback. The preview keeps its existing text/NUL behavior and never writes the file. Native checks use controlled UI/probes and logical mount paths; physical storage and panel checks remain open.
3535

36+
Directory browsing uses 64 stored path slots of 256 bytes each, including a parent entry when needed. It shows an entry-limit warning when those slots fill and counts paths skipped for length. Read/close failures mark the visited listing incomplete; reopening can retry. Open/read/close errors retain their first cause and are forwarded to the SD callback for `/sdcard` and its descendants. A null directory read with zero errno is treated as normal EOF. Native checks verify file bytes and directory handles under controlled faults; SDK mounts and physical SD/panel behavior remain open.
37+
3638
Choose a record from the current page and tap **COPY ROW** to put its original 1-128 bytes on the shared RAM clipboard. The selector and timeline use the same record numbers: positions among loaded valid rows, not physical CSV line numbers. Page/filter changes select the first matching record on the new page; changing Hex/ASCII preserves the selection. Rows of 129-256 bytes remain viewable but cannot be partially copied. Empty filters and failed copies preserve the clipboard. Record bytes can then be pasted into Byte Lab or RTU Frames; a logged RX/TX chunk may split or combine protocol frames, so a record is not a guarantee of a complete message. Copy changes neither the CSV nor the displayed byte values.
3739

3840
## CSV conventions

‎docs/testing.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Python 3 and Node.js are needed for the quick checks. The configuration checks a
1717
| Ebook page loading or navigation | [Ebook pages](#ebook-pages) and a firmware build. |
1818
| Scope/I2C capture or saved serial CSV viewers | [CSV viewer storage and UI](#csv-viewer-storage-and-ui), existing serial-copy checks and a firmware build. |
1919
| Files fallback text previews | [Files text previews](#files-text-previews) and a firmware build. |
20+
| Files directory browsing | [Files directories](#files-directories) and a firmware build. |
2021
| Offline apps, SPI or I2C editor/result flows | [Offline and peripheral UI](#offline-and-peripheral-ui). |
2122
| Serial, saved serial logs or RTU Frames | [Serial and RTU UI](#serial-and-rtu-ui). |
2223
| Modbus TCP, NTP, Wake-on-LAN, UDP or HTTP fixtures | [Network fixtures and UI](#network-fixtures-and-ui). |
@@ -176,6 +177,18 @@ UI, CSV viewer probes and the SD error callback are controlled. Logical internal
176177

177178
Generated callback/config source, full main/function/test hashes and compile/runtime logs stay under `build/file-preview/`. Linux CI retains `file-preview-logs`, including available compiler/test failure diagnostics. These 36 cases are separate from the Windows native total.
178179

180+
## Files directories
181+
182+
```powershell
183+
python tools/test_files_directory.py --compiler 'C:/path/to/clang.exe'
184+
```
185+
186+
The portable runner compiles the actual `show_files` callback. Its 31 cases exercise native directory enumeration and `stat`, file/directory classification, explicit dot entries, empty directories, mount selection, entry/path limits, open/read/close faults, first-error preservation, stale errno, callback path ownership, reopen recovery and 25 success/error/success cycles. Windows Find APIs and POSIX directory APIs provide the host entries; native dot entries are normalized before separate dot-injection checks. Every case verifies unchanged file bytes and closed directory handles. The previous callback fails 21 controls: 14 silent read/close/recovery cases, three limit disclosures and four open-error details/reporting cases.
187+
188+
UI and SD callbacks, logical mount paths and directory/stat failures are controlled. A null read with zero errno remains EOF; the fixture does not claim to detect an unreported backend error. The SD callback's filtering and SDK mount implementation are not executed. These checks do not establish LVGL allocation, panel rendering, physical SD, latency or power-loss behavior. The existing 64 path slots include parent navigation; paths stay bounded to 256 bytes.
189+
190+
Generated callback/config source, full main/function/test hashes and compile/result logs stay under `build/files-directory/`. Linux CI retains `files-directory-logs`, including available failure diagnostics. These 31 checks are separate from the Windows native total.
191+
179192
## OTA manifests
180193

181194
```powershell

‎main/main.c‎

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,19 +4311,33 @@ static void show_files(const char *path)
43114311
lv_obj_add_event_cb(up, home_clicked, LV_EVENT_CLICKED, NULL);
43124312
}
43134313

4314+
bool sd_directory = !strcmp(path, SD_PATH) || !strncmp(path, SD_PATH "/", sizeof(SD_PATH));
4315+
errno = 0;
43144316
DIR *dir = opendir(path);
43154317
if (!dir) {
4316-
lv_list_add_text(list, "Could not open directory");
4318+
int error = errno ? errno : EIO;
4319+
if (sd_directory) sd_record_error(error);
4320+
char message[96];
4321+
snprintf(message, sizeof(message), "Could not open directory: %s", strerror(error));
4322+
lv_list_add_text(list, message);
43174323
return;
43184324
}
4319-
struct dirent *entry;
4320-
while (file_path_count < 64 && (entry = readdir(dir))) {
4325+
int error = 0;
4326+
size_t skipped = 0;
4327+
while (file_path_count < sizeof(file_paths) / sizeof(file_paths[0])) {
4328+
errno = 0;
4329+
struct dirent *entry = readdir(dir);
4330+
if (!entry) {
4331+
error = errno;
4332+
break;
4333+
}
43214334
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
43224335
char *full = file_paths[file_path_count++];
43234336
size_t path_len = strlen(path);
43244337
size_t name_len = strlen(entry->d_name);
43254338
if (path_len + name_len + 2 > sizeof(file_paths[0])) {
43264339
file_path_count--;
4340+
skipped++;
43274341
continue;
43284342
}
43294343
memcpy(full, path, path_len);
@@ -4334,7 +4348,21 @@ static void show_files(const char *path)
43344348
lv_obj_t *item = lv_list_add_button(list, is_dir ? LV_SYMBOL_DIRECTORY : LV_SYMBOL_FILE, entry->d_name);
43354349
lv_obj_add_event_cb(item, file_clicked, LV_EVENT_CLICKED, full);
43364350
}
4337-
closedir(dir);
4351+
errno = 0;
4352+
if (closedir(dir) != 0 && !error) error = errno ? errno : EIO;
4353+
if (error) {
4354+
if (sd_directory) sd_record_error(error);
4355+
char message[96];
4356+
snprintf(message, sizeof(message), "Directory listing incomplete: %s", strerror(error));
4357+
lv_list_add_text(list, message);
4358+
}
4359+
if (file_path_count == sizeof(file_paths) / sizeof(file_paths[0]))
4360+
lv_list_add_text(list, "Directory entry limit reached");
4361+
if (skipped) {
4362+
char message[96];
4363+
snprintf(message, sizeof(message), "Skipped %u paths that exceed the path limit", (unsigned)skipped);
4364+
lv_list_add_text(list, message);
4365+
}
43384366
}
43394367

43404368
static void files_clicked(lv_event_t *event)

0 commit comments

Comments
 (0)