uzbek-plus/b6j/J2-rmtfs-audit.md

3.4 KiB

B6J2 — rmtfs v1.3 source audit: RAM shadow lifetime with rmtfs -r -P -v (no -s)

Source: linux/initramfs-modem1/dl/rmtfs-src = git tag v1.3 (b30a3eb). Board binary: Alpine rmtfs 1.3-r0 (aports commit 1628fd4c), sha256 1bf6b195… (same in initramfs-mm1); v1.3-specific strings present ("write to %zd bytes exceededs max size", "[RMTFS] bye from %d").

Data structures (storage.c)

  • static struct rmtfd rmtfds[MAX_CALLERS=10] — process-global static array; each slot: id, node, fd, partition, shadow_buf, shadow_len.
  • storage_read_only (= -r) is a process-global flag set once in storage_init().
  • storage_init() is called exactly once in main() (rmtfs.c:575) and only initialises slots to fd=-1 / shadow_buf=NULL.

open/read path

  • QMI OPEN (rmtfs.c:55 rmtfs_open) → storage_open(pkt->node, path) (storage.c:112).
  • storage.c:131-137: if a slot with (fd != -1 || shadow_buf) && node == node && partition == part exists, that slot is returned as-is — no open(), no read() of the partition.
  • Only for a free slot: fd_open() → with -r storage_populate_shadow_buf() (storage.c:280): open(O_RDONLY) the by-partlabel device, calloc(len), one read() of the whole partition into shadow_buf, close the fd. This is the only place the backing partition is read.

request handling

  • RW_IOVEC read → storage_pread(): with -r, memcpy from shadow_buf (storage.c:228-231).
  • RW_IOVEC write → storage_pwrite(): with -r, memcpy into shadow_buf (realloc if it grows), never touches the fd (storage.c:247-267); storage_sync() is a no-op with -r (storage.c:274).
  • QMI CLOSE → storage_close() → free(shadow_buf) (storage.c:176-188). This and storage_exit() (process exit) are the ONLY frees.

reconnect behaviour (MSS stop/start while rmtfs lives)

  • QRTR BYE (rmtfs_bye) and DEL_CLIENT (rmtfs_del_client) handlers only dbgprintf and return 0 (rmtfs.c:346-358): nothing is closed or freed.
  • ENETRESET on the QRTR socket: main() loops do { run_rmtfs() } while (ret == -ENETRESET) (rmtfs.c:581-583); run_rmtfs only re-opens/re-publishes the socket; storage_init/exit are outside the loop → shadows survive.
  • Without -s: rprocfd = -1 → rmtfs never writes remoteproc state; SIGTERM → loop break → storage_exit() → process exits (rmtfs.c:453-455).
  • The modem on this board never sends QMI CLOSE: session-28 rmtfs.log (11 MSS boots) has 0 "close" lines; each MSS stop shows only del_client + bye from 0. The next MSS boot re-opens the same 4 paths from the same node 0 and gets the same caller ids 0..3.

Answer

With a single long-lived rmtfs -r -P -v process, a modem_fs1/fs2/fsg/fsc write received from MSS stays in that slot's shadow_buf for the lifetime of the process, and a later MSS boot (same node, no CLOSE) is served from that modified RAM copy — the backing partitions are NOT re-read on MSS reconnect. They are re-read only by a new rmtfs process (or after an explicit QMI CLOSE, which this modem does not send). The old B6 flow started a new rmtfs per cycle, so every MSS boot saw the on-disk EFS image. → GO for B6J3.

Caveats: (1) a hypothetical CLOSE from the modem would free the shadow (debug printf in rmtfs_close also passes an int for %s — would be UB with -v); not observed. (2) The shadow is only the EFS part of the modem's persistent state; USIM files (EF_EPSLOCI etc.) persist across MSS restarts in both flows.