|
os.rename(tf.name, filename) |
When a system crashes or is forcefully rebooted (e.g. SIGKILL, hard reset) right after cloud-init writes instance cache files, network-config.json can become a 0-byte file on reboot.
On the next boot, cloud-init crashes on an unhandled JSONDecodeError and fails to initialize networking.
Root Cause
- Missing
fsync before rename: cloudinit/atomic_helper.py writes to a tempfile and calls os.rename() without tf.flush() or os.fsync(). On filesystems like ext4 (with delayed allocation), rename commits directory metadata to the journal while dirty data remains in volatile RAM. A sudden crash before background writeback leaves an empty file (Size: 0, Blocks: 0).
- Missing error handling:
cloudinit/stages.py:448 calls util.load_json() without try...except, raising unhandled json.decoder.JSONDecodeError on empty/corrupted cache.
Suggested Fix
-
cloudinit/atomic_helper.py: Flush and sync before rename:
tf.write(content)
tf.flush()
try:
os.fsync(tf.fileno())
except OSError:
pass
os.chmod(tf.name, mode)
os.rename(tf.name, filename)
-
cloudinit/stages.py: Handle corrupted/empty JSON gracefully
if os.path.isfile(net_cfg_fname):
try:
content = util.load_text_file(net_cfg_fname)
if content:
return util.load_json(content)
except (json.decoder.JSONDecodeError, ValueError) as exc:
LOG.warning("Failed to parse cached network config at %s: %s", net_cfg_fname, exc)
cloud-init/cloudinit/atomic_helper.py
Line 78 in 494f7de
When a system crashes or is forcefully rebooted (e.g.
SIGKILL, hard reset) right aftercloud-initwrites instance cache files,network-config.jsoncan become a 0-byte file on reboot.On the next boot,
cloud-initcrashes on an unhandledJSONDecodeErrorand fails to initialize networking.Root Cause
fsyncbeforerename:cloudinit/atomic_helper.pywrites to a tempfile and callsos.rename()withouttf.flush()oros.fsync(). On filesystems like ext4 (with delayed allocation),renamecommits directory metadata to the journal while dirty data remains in volatile RAM. A sudden crash before background writeback leaves an empty file (Size: 0, Blocks: 0).cloudinit/stages.py:448callsutil.load_json()withouttry...except, raising unhandledjson.decoder.JSONDecodeErroron empty/corrupted cache.Suggested Fix
cloudinit/atomic_helper.py: Flush and sync before rename:
tf.write(content)
tf.flush()
try:
os.fsync(tf.fileno())
except OSError:
pass
os.chmod(tf.name, mode)
os.rename(tf.name, filename)
cloudinit/stages.py: Handle corrupted/empty JSON gracefully
if os.path.isfile(net_cfg_fname):
try:
content = util.load_text_file(net_cfg_fname)
if content:
return util.load_json(content)
except (json.decoder.JSONDecodeError, ValueError) as exc:
LOG.warning("Failed to parse cached network config at %s: %s", net_cfg_fname, exc)