diff --git a/cloudinit/util.py b/cloudinit/util.py index 1fadd196291..ae3311e3e28 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1887,7 +1887,12 @@ def is_link(path): def sym_link(source, link, force=False): LOG.debug("Creating symbolic link from %r => %r", link, source) if force and os.path.lexists(link): - del_file(link) + # Provide atomic update of symlink to avoid races with status --wait + # LP: #1962150 + tmp_link = os.path.join(os.path.dirname(link), "tmp" + rand_str(8)) + os.symlink(source, tmp_link) + os.replace(tmp_link, link) + return os.symlink(source, link) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 3f3079b04e7..db6e2bc4be6 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -376,13 +376,16 @@ def test_sym_link_source_exists(self): tmpd = self.tmp_dir() link = self.tmp_path("link", tmpd) target = self.tmp_path("target", tmpd) + target2 = self.tmp_path("target2", tmpd) util.write_file(target, "hello") + util.write_file(target2, "hello2") util.sym_link(target, link) self.assertTrue(os.path.exists(link)) - util.sym_link(target, link, force=True) + util.sym_link(target2, link, force=True) self.assertTrue(os.path.exists(link)) + self.assertEqual("hello2", util.load_file(link)) def test_sym_link_dangling_link(self): tmpd = self.tmp_dir()