From 479c7201b61d674e54e2ee7c347cd90fc0aaf1d3 Mon Sep 17 00:00:00 2001
From: Adam Collard <sparkiegeek@users.noreply.github.com>
Date: Fri, 8 Apr 2022 20:20:18 +0100
Subject: [PATCH 8/8] util: atomically update sym links to avoid Suppress
 FileNotFoundError when reading status (#1298)

Reference:https://github.com/canonical/cloud-init/commit/0450a1faff9e5095e6da0865916501772b3972e9
Conflict:change tests/unittests/test_util.py not cloudinit/tests/test_util.py.

Atomically update the desired link file from a temporary file
when a stale link already exists.

This avoids FileNotFound errors due to races with
cloud-init status --wait when the symlink
/run/cloud-init/status.json already exists.

LP:1962150
---
 cloudinit/tests/test_util.py | 5 ++++-
 cloudinit/util.py            | 7 ++++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
index 671b8bc..5fb2508 100644
--- a/cloudinit/tests/test_util.py
+++ b/cloudinit/tests/test_util.py
@@ -328,13 +328,16 @@ class TestSymlink(CiTestCase):
         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()
diff --git a/cloudinit/util.py b/cloudinit/util.py
index d5e8277..0e0fc04 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1797,7 +1797,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)
 
 
-- 
2.40.0

