From 58774990868761750874fc8c21af0ccabefb1e53 Mon Sep 17 00:00:00 2001
From: chenjiayi <chenjiayi22@huawei.com>
Date: Wed, 21 Jun 2023 23:43:12 +0800
Subject: [PATCH] fix(libbasic): create symlink atomically

Create a temporary symlink and rename it into the final symlink.
If there exists a symlink with the same name already, the symlink
will be overrided. If any error occurs, the original symlink will
be reserved.
---
 libs/basic/Cargo.toml     |  1 +
 libs/basic/src/fs_util.rs | 20 ++++++++++++++++----
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml
index 64fbfba..7557d37 100644
--- a/libs/basic/Cargo.toml
+++ b/libs/basic/Cargo.toml
@@ -20,6 +20,7 @@ caps = "0.5.5"
 lazy_static = "1.4.0"
 bitflags = "1.3.2"
 pkg-config = "0.3"
+rand = "0.4.6"
 
 [dev-dependencies]
 libtests = { path = "../libtests" }
diff --git a/libs/basic/src/fs_util.rs b/libs/basic/src/fs_util.rs
index 5f34e9a..6a6378d 100644
--- a/libs/basic/src/fs_util.rs
+++ b/libs/basic/src/fs_util.rs
@@ -15,11 +15,12 @@
 use crate::{error::*, format_proc_fd_path};
 use libc::{fchownat, mode_t, timespec, AT_EMPTY_PATH, S_IFLNK, S_IFMT};
 use nix::{
-    fcntl::OFlag,
+    fcntl::{renameat, OFlag},
     sys::stat::{fstat, Mode},
-    unistd::{Gid, Uid},
+    unistd::{unlinkat, Gid, Uid, UnlinkatFlags},
 };
 use pathdiff::diff_paths;
+use rand::Rng;
 use std::{fs::remove_dir, io::ErrorKind, os::unix::prelude::PermissionsExt, path::Path};
 
 /// open the parent directory of path
@@ -36,7 +37,7 @@ pub fn symlink(target: &str, link: &str, relative: bool) -> Result<()> {
     let link_path = Path::new(&link);
     let target_path = Path::new(&target);
 
-    let (target_path, fd) = if relative {
+    let (target_path, dirfd) = if relative {
         let link_path_parent = link_path.parent().ok_or(Error::NotExisted {
             what: format!("{}'s parent", link_path.to_string_lossy()),
         })?;
@@ -53,7 +54,18 @@ pub fn symlink(target: &str, link: &str, relative: bool) -> Result<()> {
         (target_path.to_path_buf(), None)
     };
 
-    nix::unistd::symlinkat(target_path.as_path(), fd, link_path).context(NixSnafu)
+    let mut rng = rand::thread_rng();
+
+    let tmp_to = format!("{}.{}", link, rng.gen::<u32>());
+
+    nix::unistd::symlinkat(target_path.as_path(), dirfd, tmp_to.as_str()).context(NixSnafu)?;
+
+    if let Err(e) = renameat(dirfd, tmp_to.as_str(), dirfd, link_path) {
+        let _ = unlinkat(dirfd, tmp_to.as_str(), UnlinkatFlags::NoRemoveDir);
+        return Err(Error::Nix { source: e });
+    }
+
+    Ok(())
 }
 
 /// chmod based on fd opened with O_PATH
-- 
2.33.0

