- P2-20:_guest_env_delivered 加专用锁,读写只走 _delivered_secrets/_mark/_clear 三个入口,并发 hammer 测试钉死 - P2-19:_record_net_entries 失败发 net_registry_write_failed 独立审计并返回 False;_boot_vm 见 False 即拒起(wake 随后清半醒 VM) - P2-17:net.json 删失败发 vm_net_registry_delete_failed 审计; _write_dirty_marker 失败发 dirty_marker_write_failed 审计并返回 False,stop 不抛的契约不变 - P2-18:新增 cloud/chv/atomic_file.py(mkstemp 随机名 + 文件 fsync + rename + 目录 fsync),net.json/known_hosts/ snapshots.json/pool registry/脏标记/spill 全部走它,固定 .tmp 清零 - H1 附带:NO_PROXY 实测确有覆盖 bug(env 重复赋值以后者为准, 调用方 NO_PROXY 被 egress 的 localhost 值静默覆盖),改为合并 单条发出;egress 已设时调用方代理变量(含小写)一律不发 - 测试:新增 cloud/tests/test_chv_residual.py(23 项),旧 closeup 两处钉死旧语义的测试按新语义更新
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
"""CHV 宿主侧小文件的原子写(P2-18 收尾)。
|
||
|
||
此前 net.json / snapshots.json / pool registry /
|
||
known_hosts 各自用固定名 `<path>.tmp` + 直接 os.replace,
|
||
存在两个问题:
|
||
|
||
1. 固定 tmp 名:同一进程内两个写者(或一次崩溃残留的
|
||
.tmp)会互相覆盖/误读;不同 VM 的文件虽在不同目录,
|
||
但同一目录内的快照登记与 net 登记仍共用可预测的
|
||
tmp 名,崩溃后残留的 .tmp 会被下一次写静默覆盖,
|
||
丢了“上次写了一半”的信号。
|
||
2. 无 fsync:os.replace 只保证目录项原子,不保证内容
|
||
已落盘;宿主断电/崩溃后新文件可能回到旧内容或
|
||
0 字节,而调用方已认为登记成功。
|
||
|
||
本模块是唯一写路径:mkstemp 随机名(0600)→ 写 →
|
||
flush + fsync 文件 → os.replace → fsync 父目录。
|
||
写失败时删临时文件再原样抛错,调用方按各自语义
|
||
处理(审计/拒绝/脏标记),本模块不吞错、不记日志。
|
||
"""
|
||
|
||
import os
|
||
import tempfile
|
||
|
||
|
||
def atomic_write(path: str, data: bytes, mode: int = 0o600) -> None:
|
||
"""原子写 bytes 到 path(随机 tmp 名 + 双 fsync)。
|
||
|
||
mode 是最终文件的权限(默认 0600:这些文件都在
|
||
0700 的 state_dir 内、且含登记/密钥类内容,按敏感
|
||
文件对待;调用方要放宽时显式传)。
|
||
"""
|
||
directory = os.path.dirname(path) or "."
|
||
fd, tmp_path = tempfile.mkstemp(prefix=".tmp-", dir=directory)
|
||
try:
|
||
os.fchmod(fd, mode)
|
||
with os.fdopen(fd, "wb") as fh:
|
||
fh.write(data)
|
||
fh.flush()
|
||
os.fsync(fh.fileno())
|
||
os.replace(tmp_path, path)
|
||
_fsync_dir(directory)
|
||
except BaseException:
|
||
try:
|
||
os.unlink(tmp_path)
|
||
except OSError:
|
||
pass
|
||
raise
|
||
|
||
|
||
def atomic_write_text(path: str, text: str, mode: int = 0o600) -> None:
|
||
"""atomic_write 的 str 版(UTF-8 编码)。"""
|
||
atomic_write(path, text.encode("utf-8"), mode=mode)
|
||
|
||
|
||
def _fsync_dir(directory: str) -> None:
|
||
"""fsync 父目录,让 rename 本身落盘(best-effort:
|
||
某些文件系统不支持目录 fsync,失败即跳过——文件
|
||
内容的 fsync 已在 atomic_write 里做完,目录 fsync
|
||
丢了只影响“rename 是否在崩溃后可见”,不影响内容)。
|
||
"""
|
||
try:
|
||
dir_fd = os.open(directory, os.O_RDONLY)
|
||
except OSError:
|
||
return
|
||
try:
|
||
os.fsync(dir_fd)
|
||
except OSError:
|
||
pass
|
||
finally:
|
||
os.close(dir_fd)
|