fix(companion): scopes, room bot backlog, DM flood fallback, atomic trail saves

- Removing a scope moves the channels that used it to the default, not to
  unscoped.
- The room bot ignores posts older than 5 minutes: the backlog a room
  replays after login no longer gets answers.
- Adverts take the default scope from the scope list, as messages do; the
  legacy fields always mirror it, and CMD_SET_DEFAULT_FLOOD_SCOPE with a
  full list answers TABLE_FULL.
- The last DM resend drops the direct path and floods.
- /trail and /waypoints are written through a temp file.
- An admin login timeout keeps the saved password.
- History records read from the SD card are sanitised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-12 17:54:38 +02:00
co-authored by Claude Opus 5.5
parent fd45c2219b
commit cd294613ea
8 changed files with 63 additions and 43 deletions
@@ -193,11 +193,19 @@ private:
uint32_t run = m - got < h.cap - slot ? m - got : h.cap - slot;
if (fseek(f, sizeof(Hdr) + (long)slot * sizeof(E), SEEK_SET) != 0) break;
size_t r = fread(&out[got], sizeof(E), run, f);
for (size_t i = 0; i < r; i++) sanitize(out[got + i]);
got += (uint32_t)r;
if (r != run) break;
}
return got;
}
// The card comes out and goes on a computer: a damaged or edited record
// must not leave the text unterminated or a path longer than its buffer.
template <typename E>
static void sanitize(E& e) {
e.text[sizeof(e.text) - 1] = '\0';
if (((e.path_len >> 6) + 1) * (e.path_len & 63) > (int)sizeof(e.path)) e.path_len = 0;
}
static bool writeHdr(FILE* f, const Hdr& h) {
return fseek(f, 0, SEEK_SET) == 0 && fwrite(&h, sizeof(h), 1, f) == 1;
}