diff --git a/.gitattributes b/.gitattributes index 290642ce1a..8a73904ea3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ # The contributor harness hashes provenance inputs byte-for-byte. Keep text # files in this boundary on LF even when Windows enables core.autocrlf. harness/ruview/** text=auto eol=lf +harness/homecore/** text=auto eol=lf diff --git a/firmware/esp32-csi-node/provision.py b/firmware/esp32-csi-node/provision.py index 46a7aab310..a57fa3b14c 100644 --- a/firmware/esp32-csi-node/provision.py +++ b/firmware/esp32-csi-node/provision.py @@ -136,9 +136,16 @@ def _default_state_dir() -> str: return os.path.join(base, "wifi-densepose", "esp32-provision-state") +_RESERVED_DEVICE_RE = re.compile( + r"^(con|prn|aux|nul|com[1-9]|lpt[1-9])$", re.IGNORECASE +) + + def _state_path_for(port: str, state_dir: str) -> str: """File path for a given serial port. Sanitize the port for filesystem use.""" safe = port.replace("/", "_").replace(":", "_").replace("\\", "_") + if _RESERVED_DEVICE_RE.match(safe): + safe = f"_{safe}" return os.path.join(state_dir, f"{safe}.json") @@ -306,7 +313,13 @@ def load_state(port: str, state_dir: str) -> dict: """Return the merged-state dict for `port`, or `{}` if absent / unreadable.""" path = _state_path_for(port, state_dir) if not os.path.isfile(path): - return {} + legacy = os.path.join( + state_dir, f"{port.replace('/', '_').replace(':', '_').replace('\\', '_')}.json" + ) + if legacy != path and os.path.isfile(legacy): + path = legacy + else: + return {} try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) @@ -443,6 +456,11 @@ def load_board_state(port: str, mac, state_dir: str): legacy = _state_path_for(port, state_dir) if os.path.isfile(legacy): return load_state(port, state_dir), legacy + unprefixed = os.path.join( + state_dir, f"{port.replace('/', '_').replace(':', '_').replace('\\', '_')}.json" + ) + if unprefixed != legacy and os.path.isfile(unprefixed): + return load_state(port, state_dir), unprefixed return {}, None diff --git a/firmware/esp32-csi-node/tests/test_provision_state.py b/firmware/esp32-csi-node/tests/test_provision_state.py index e55270e991..33af5ecc1c 100644 --- a/firmware/esp32-csi-node/tests/test_provision_state.py +++ b/firmware/esp32-csi-node/tests/test_provision_state.py @@ -122,7 +122,16 @@ def test_slashes_in_port_are_safe(self): def test_windows_com_port_is_safe(self): path = provision._state_path_for("COM7", "/tmp/x") - self.assertTrue(path.endswith("COM7.json")) + self.assertTrue(path.endswith("_COM7.json")) + + def test_reserved_dos_device_names_are_prefixed(self): + for name in ["CON", "PRN", "AUX", "NUL", "COM1", "COM9", "LPT1"]: + path = provision._state_path_for(name, "/tmp/x") + self.assertEqual(os.path.basename(path), f"_{name}.json") + + def test_non_reserved_ports_remain_unprefixed(self): + path = provision._state_path_for("/dev/ttyUSB0", "/tmp/x") + self.assertEqual(os.path.basename(path), "_dev_ttyUSB0.json") if __name__ == "__main__":