mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-07-01 03:41:46 +00:00
b06b3650ae
OTA hardening follow-up to the patch_nimble.py hardcoded-"tdeck" bug that broke tdeck-ota linking (undefined reference to nimble_host_reset_reason): - Add _build_helpers.env_libdeps_dir(env, *parts): the single per-environment libdeps path resolver (.pio/libdeps/<PIOENV>/...). Converted all five pre-scripts (patch_nimble/msgpack/filestore/littlefs_paths, sync_file_libdeps) to use it, so the env component can no longer be hand-rolled/hardcoded wrong per script. - "OTA: Ready" log -> "OTA: wireless flash service started (pyxis-tdeck:3232)": ArduinoOTA.begin() is void and can't confirm a ready state, so log the target instead of claiming readiness we can't verify. Verified: both tdeck and tdeck-ota build, and the NimBLE patch lands in EACH env's own libdeps tree (nimble_host_reset_reason count 2 in both). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
165 lines
7.2 KiB
Python
165 lines
7.2 KiB
Python
"""
|
|
PlatformIO pre-build script: patches the libdeps copy of microStore
|
|
FileStore.h before each build.
|
|
|
|
Three purposes:
|
|
|
|
1. Adds diagnostic printfs to `exists()` and `put()` for tracking down
|
|
the path-table-store bug where exists() returns false right after a
|
|
successful put for the same key. Temporary; remove once that
|
|
investigation is closed. Gated behind PYXIS_FILESTORE_DIAG=1.
|
|
|
|
2. Silences the spammy "[ustore] get: key not found in index" print
|
|
that fires on every path-table miss. RNS hits path-store lookups
|
|
constantly on every incoming packet — during an active LXST call
|
|
that print floods USB CDC at hundreds of lines/sec, saturating the
|
|
serial buffer and starving T:CALL_QOS responses (#75). The print
|
|
is unconditional in upstream microStore, so we patch it out here.
|
|
|
|
3. Closes `active_file` before unlinking segment files in
|
|
`finalize_compaction()` and `clear()`. Without this, on LittleFS
|
|
/ FAT (any FS that doesn't auto-close on unlink) the FD leaks —
|
|
over enough compactions the device can't open new files. Local
|
|
patch pending the upstream fix landing.
|
|
"""
|
|
Import("env")
|
|
import os, sys
|
|
sys.path.insert(0, env.get("PROJECT_DIR", "."))
|
|
from _build_helpers import env_libdeps_dir # per-env libdeps path; never hardcode the env
|
|
|
|
FILESTORE_H = env_libdeps_dir(env, "microStore", "include", "microStore", "FileStore.h")
|
|
|
|
OLD = """\tbool exists(const uint8_t* key, uint8_t key_len)
|
|
\t{
|
|
if (!isValid()) return false;
|
|
\t\tif(key_len > USTORE_MAX_KEY_LEN) return false;
|
|
\t\tIndexValue* e = index_find(key, key_len);
|
|
\t\tif (!e) return false;
|
|
\t\tif (is_ttl_expired_(e->timestamp, e->ttl)) { index_remove(key, key_len); return false; }
|
|
\t\treturn true;
|
|
\t}"""
|
|
|
|
NEW = """\t// pyxis-local diagnostic helper, only compiled under PYXIS_FILESTORE_DIAG
|
|
\t// and only used by the printfs below. Hex-encodes a binary key using just
|
|
\t// standard C so the diagnostic build is self-contained (no external bin_str).
|
|
\t// Static buffer is fine here: each printf evaluates it exactly once.
|
|
\tstatic const char* bin_str(const uint8_t* d, uint8_t n) {
|
|
\t\tstatic char b[2 * USTORE_MAX_KEY_LEN + 1];
|
|
\t\tstatic const char hex[] = "0123456789abcdef";
|
|
\t\tif (n > USTORE_MAX_KEY_LEN) n = USTORE_MAX_KEY_LEN;
|
|
\t\tfor (uint8_t i = 0; i < n; i++) { b[2 * i] = hex[(d[i] >> 4) & 0xF]; b[2 * i + 1] = hex[d[i] & 0xF]; }
|
|
\t\tb[2 * n] = '\\0';
|
|
\t\treturn b;
|
|
\t}
|
|
\tbool exists(const uint8_t* key, uint8_t key_len)
|
|
\t{
|
|
\t\tif (!isValid()) { printf("[ustore] exists: !isValid len=%u idx_size=%zu store=%p\\n", (unsigned)key_len, _index.size(), (void*)this); return false; }
|
|
\t\tif(key_len > USTORE_MAX_KEY_LEN) { printf("[ustore] exists: key too long\\n"); return false; }
|
|
\t\tIndexValue* e = index_find(key, key_len);
|
|
\t\tif (!e) { printf("[ustore] exists: not_in_index len=%u key=%s idx_size=%zu store=%p\\n", (unsigned)key_len, bin_str(key, key_len), _index.size(), (void*)this); return false; }
|
|
\t\tif (is_ttl_expired_(e->timestamp, e->ttl)) { printf("[ustore] exists: ttl_expired ts=%u ttl=%u now=%u\\n", e->timestamp, e->ttl, microStore::time()); index_remove(key, key_len); return false; }
|
|
\t\tprintf("[ustore] exists: found key=%s store=%p\\n", bin_str(key, key_len), (void*)this);
|
|
\t\treturn true;
|
|
\t}"""
|
|
|
|
PUT_OLD = """\t\tindex_insert(key, key_len, current_segment, offset, ts, ttl);
|
|
|
|
\t\t// Enforce max_recs:"""
|
|
|
|
PUT_NEW = """\t\tindex_insert(key, key_len, current_segment, offset, ts, ttl);
|
|
\t\tprintf("[ustore] put: index_insert done key=%s idx_size=%zu store=%p\\n", bin_str(key, key_len), _index.size(), (void*)this);
|
|
|
|
\t\t// Enforce max_recs:"""
|
|
|
|
SPAMMY_OLD = '\t\t\tprintf("[ustore] get: key not found in index\\n");'
|
|
SPAMMY_NEW = '\t\t\t/* silenced — fires on every path-store miss, floods USB CDC */'
|
|
|
|
# FD-leak fix: close active_file before unlinking segments. Two sites.
|
|
FDLEAK_FINALIZE_OLD = """\tvoid finalize_compaction()
|
|
\t{
|
|
\t\tchar tmp_name[USTORE_MAX_FILENAME_LEN]; snprintf(tmp_name, sizeof(tmp_name), \"%s_compact.tmp\", base_prefix);
|
|
\t\tchar seg0[USTORE_MAX_FILENAME_LEN]; segment_name(0, seg0);
|
|
|
|
\t\t// Remove all existing segments, then rename tmp → seg0.
|
|
\t\tfor (uint32_t i = 0; i < _segment_count; i++) {
|
|
\t\t\tchar sname[USTORE_MAX_FILENAME_LEN]; segment_name(i, sname);
|
|
\t\t\t_filesystem.remove(sname);
|
|
\t\t}"""
|
|
FDLEAK_FINALIZE_NEW = """\tvoid finalize_compaction()
|
|
\t{
|
|
\t\tchar tmp_name[USTORE_MAX_FILENAME_LEN]; snprintf(tmp_name, sizeof(tmp_name), \"%s_compact.tmp\", base_prefix);
|
|
\t\tchar seg0[USTORE_MAX_FILENAME_LEN]; segment_name(0, seg0);
|
|
|
|
\t\t// pyxis-local: close active_file before unlinking, otherwise the FD
|
|
\t\t// leaks on LittleFS / FAT.
|
|
\t\tif (active_file) active_file.close();
|
|
|
|
\t\t// Remove all existing segments, then rename tmp → seg0.
|
|
\t\tfor (uint32_t i = 0; i < _segment_count; i++) {
|
|
\t\t\tchar sname[USTORE_MAX_FILENAME_LEN]; segment_name(i, sname);
|
|
\t\t\t_filesystem.remove(sname);
|
|
\t\t}"""
|
|
|
|
FDLEAK_CLEAR_OLD = """\t\tif (index_file) index_file.close();
|
|
|
|
\t\tfor(uint32_t i = 0; i < _segment_count; i++)
|
|
\t\t{
|
|
\t\t\tsegment_name(i,name);
|
|
\t\t\t_filesystem.remove(name);
|
|
\t\t}"""
|
|
FDLEAK_CLEAR_NEW = """\t\tif (index_file) index_file.close();
|
|
\t\t// pyxis-local: same active_file FD-leak fix as finalize_compaction().
|
|
\t\tif (active_file) active_file.close();
|
|
|
|
\t\tfor(uint32_t i = 0; i < _segment_count; i++)
|
|
\t\t{
|
|
\t\t\tsegment_name(i,name);
|
|
\t\t\t_filesystem.remove(name);
|
|
\t\t}"""
|
|
|
|
DIAG_ENABLED = os.environ.get("PYXIS_FILESTORE_DIAG", "0") == "1"
|
|
|
|
def patch(content):
|
|
out = content
|
|
# Silence patch always runs.
|
|
if SPAMMY_OLD in out:
|
|
out = out.replace(SPAMMY_OLD, SPAMMY_NEW)
|
|
print("PATCH: FileStore.h: silenced 'key not found in index' spam")
|
|
elif "silenced — fires on every path-store miss" in content:
|
|
print("PATCH: FileStore.h: 'key not found' spam already silenced")
|
|
# FD-leak fix always runs.
|
|
if FDLEAK_FINALIZE_OLD in out:
|
|
out = out.replace(FDLEAK_FINALIZE_OLD, FDLEAK_FINALIZE_NEW)
|
|
print("PATCH: FileStore.h: closed active_file in finalize_compaction()")
|
|
elif "pyxis-local: close active_file before unlinking" in content:
|
|
print("PATCH: FileStore.h: finalize_compaction() FD-leak fix already applied")
|
|
if FDLEAK_CLEAR_OLD in out:
|
|
out = out.replace(FDLEAK_CLEAR_OLD, FDLEAK_CLEAR_NEW)
|
|
print("PATCH: FileStore.h: closed active_file in clear()")
|
|
elif "pyxis-local: same active_file FD-leak fix" in content:
|
|
print("PATCH: FileStore.h: clear() FD-leak fix already applied")
|
|
# Diagnostic patches only when explicitly requested.
|
|
if not DIAG_ENABLED:
|
|
return out
|
|
if OLD in out:
|
|
out = out.replace(OLD, NEW)
|
|
print("PATCH: FileStore.h: exists() diagnostics added")
|
|
elif "store=%p" in content and "exists:" in content:
|
|
print("PATCH: FileStore.h: exists() already patched")
|
|
if PUT_OLD in out:
|
|
out = out.replace(PUT_OLD, PUT_NEW)
|
|
print("PATCH: FileStore.h: put-after-insert diagnostics added")
|
|
elif "put: index_insert done" in content:
|
|
print("PATCH: FileStore.h: put-after-insert already patched")
|
|
return out
|
|
|
|
if os.path.exists(FILESTORE_H):
|
|
with open(FILESTORE_H) as f:
|
|
content = f.read()
|
|
new = patch(content)
|
|
if new != content:
|
|
with open(FILESTORE_H, "w") as f:
|
|
f.write(new)
|
|
else:
|
|
print("PATCH: FileStore.h not found, skipping")
|