From 58774990868761750874fc8c21af0ccabefb1e53 Mon Sep 17 00:00:00 2001 From: chenjiayi 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::()); + + 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