From 11b505249041b6368eaac0b5699c455599a9565a Mon Sep 17 00:00:00 2001
From: chenjiayi <chenjiayi22@huawei.com>
Date: Tue, 27 Jun 2023 05:01:37 +0800
Subject: [PATCH] fix(libbasic): try to follow the fd path to make the
 utimensat work

In devmaster threads, utimensat will fail with errno of ENOENT, which means
the file path does not exist, if the fd path is used. The fd path is a symlink to
the opened real file. It is weired because the fd path really exists but utimesat
can not find it. To avoid the failure, we have no choice but to follow the fd path
symlink to the real file, and update the timestamp directly on it.
---
 libs/basic/src/error.rs   |  7 +++-
 libs/basic/src/fs_util.rs | 78 ++++++++++++++++++++++++++-------------
 2 files changed, 57 insertions(+), 28 deletions(-)

diff --git a/libs/basic/src/error.rs b/libs/basic/src/error.rs
index 2730883..cfa89f8 100644
--- a/libs/basic/src/error.rs
+++ b/libs/basic/src/error.rs
@@ -44,6 +44,9 @@ pub enum Error {
     #[snafu(display("procfs: {}", source))]
     Proc { source: procfs::ProcError },
 
+    #[snafu(display("NulError: '{}'", source))]
+    NulError { source: std::ffi::NulError },
+
     #[snafu(display("Error parsing from string: {}", source))]
     Parse {
         source: Box<dyn std::error::Error + Send + Sync>,
@@ -52,13 +55,13 @@ pub enum Error {
     #[snafu(display("Invalid naming scheme string: {}", what))]
     ParseNamingScheme { what: String },
 
-    #[snafu(display("Not exist): '{}'.", what))]
+    #[snafu(display("Not exist: '{}'.", what))]
     NotExisted { what: String },
 
     #[snafu(display("Invalid: '{}'.", what))]
     Invalid { what: String },
 
-    #[snafu(display("OtherError): '{}'.", msg))]
+    #[snafu(display("OtherError: '{}'.", msg))]
     Other { msg: String },
 }
 
diff --git a/libs/basic/src/fs_util.rs b/libs/basic/src/fs_util.rs
index 6a6378d..3f72658 100644
--- a/libs/basic/src/fs_util.rs
+++ b/libs/basic/src/fs_util.rs
@@ -15,13 +15,15 @@
 use crate::{error::*, format_proc_fd_path};
 use libc::{fchownat, mode_t, timespec, AT_EMPTY_PATH, S_IFLNK, S_IFMT};
 use nix::{
-    fcntl::{renameat, OFlag},
+    fcntl::{readlink, renameat, OFlag},
     sys::stat::{fstat, Mode},
     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};
+use std::{
+    ffi::CString, fs::remove_dir, io::ErrorKind, os::unix::prelude::PermissionsExt, path::Path,
+};
 
 /// open the parent directory of path
 pub fn open_parent(path: &Path, flags: OFlag, mode: Mode) -> Result<i32> {
@@ -148,32 +150,56 @@ pub fn fchmod_and_chown(
     Ok(do_chown || do_chmod)
 }
 
-/// if ts are not provided, use the current timestamp by default.
+/// Update the timestamp of a file with fd. If 'ts' is not provided, use the current timestamp by default.
 pub fn futimens_opath(fd: i32, ts: Option<[timespec; 2]>) -> Result<()> {
-    let r = unsafe {
-        libc::utimensat(
-            libc::AT_FDCWD,
-            format_proc_fd_path!(fd).as_ptr() as *const libc::c_char,
-            &ts.unwrap_or([
-                timespec {
-                    tv_sec: 0,
-                    tv_nsec: libc::UTIME_NOW,
-                },
-                timespec {
-                    tv_sec: 0,
-                    tv_nsec: libc::UTIME_NOW,
-                },
-            ])[0],
-            0,
-        )
-    };
-    if r < 0 {
-        Err(Error::Nix {
-            source: nix::Error::from_i32(std::io::Error::last_os_error().raw_os_error().unwrap()),
-        })
-    } else {
-        Ok(())
+    let fd_path = format_proc_fd_path!(fd);
+    let c_string =
+        CString::new(fd_path.clone()).map_err(|e| crate::Error::NulError { source: e })?;
+    let times = ts.unwrap_or([
+        timespec {
+            tv_sec: 0,
+            tv_nsec: libc::UTIME_NOW,
+        },
+        timespec {
+            tv_sec: 0,
+            tv_nsec: libc::UTIME_NOW,
+        },
+    ])[0];
+
+    if unsafe { libc::utimensat(libc::AT_FDCWD, c_string.as_ptr(), &times, 0) } < 0 {
+        let errno = nix::Error::from_i32(
+            std::io::Error::last_os_error()
+                .raw_os_error()
+                .unwrap_or_default(),
+        );
+
+        if errno == nix::Error::ENOENT {
+            /*
+             * In devmaster threads, utimensat will fail with errno of ENOENT, which means
+             * the file path does not exist, if the fd path is used. The fd path is a symlink to
+             * the opened real file. It is weird because the fd path really exists but utimesat
+             * can not find it. To avoid the failure, we try to follow the fd path symlink to the
+             * real file and update the timestamp directly on it.
+             */
+            let target = readlink(fd_path.as_str()).unwrap();
+            let c_string = target
+                .to_str()
+                .ok_or(Error::Nix { source: errno })
+                .map(|s| unsafe { libc::strdup(s.as_ptr() as *const libc::c_char) })?;
+
+            if unsafe { libc::utimensat(libc::AT_FDCWD, c_string, &times, 0) } < 0 {
+                return Err(Error::Nix {
+                    source: nix::Error::from_i32(
+                        std::io::Error::last_os_error().raw_os_error().unwrap(),
+                    ),
+                });
+            }
+        } else {
+            return Err(Error::Nix { source: errno });
+        }
     }
+
+    Ok(())
 }
 
 /// recursively remove parent directories until specific directory
-- 
2.33.0

