From 30b999c5a2b6ce67734e882942609a5e4a4d84cf Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 7 Oct 2026 19:57:03 +0200 Subject: interfaces: T3871: make the interface map survive a crash, not just a rename Replacing the map was atomic but not durable: neither its contents nor the rename were forced to the disk, so a machine losing power shortly after a boot could come back to an empty or truncated file. Names are then derived from the hardware again, which only gives the same answer where they happened to follow slot order - anywhere else an address moves to another cable. Force the contents out before the file is put in place, and the directory entry after, so what comes back is either the names it had before or the ones just written. --- python/vyos/ifconfig/ifname_store.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/ifname_store.py b/python/vyos/ifconfig/ifname_store.py index 5a0a29398..b0ee3df35 100644 --- a/python/vyos/ifconfig/ifname_store.py +++ b/python/vyos/ifconfig/ifname_store.py @@ -289,7 +289,9 @@ def config_dir_is_mounted(path: Path = None) -> bool: def save_store(store: dict, path: Path = None) -> bool: - """Write atomically, so an interrupted boot leaves no half-written file. + """Write atomically and durably, so a boot cut short leaves either the + previous names or the new ones, never a half-written file and never a + rename the disk has not seen. Returns False without writing on a degraded boot: names are still resolved for this boot, they just must not be persisted. @@ -305,6 +307,8 @@ def save_store(store: dict, path: Path = None) -> bool: with os.fdopen(fd, 'w') as f: json.dump(store, f, indent=2, sort_keys=True) f.write('\n') + f.flush() + os.fsync(f.fileno()) os.chmod(tmp, 0o664) try: os.chown(tmp, -1, get_cfg_group_id()) @@ -312,6 +316,13 @@ def save_store(store: dict, path: Path = None) -> bool: # not fatal, the store is read by root at boot pass os.replace(tmp, path) + # the rename itself has to reach the disk, or a boot cut short here + # comes back to the name it had before + dir_fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(dir_fd) + finally: + os.close(dir_fd) except Exception: Path(tmp).unlink(missing_ok=True) raise -- cgit v1.2.3