From 35ee9283c60a2d3e7d5ae2bcb9db31c71856d645 Mon Sep 17 00:00:00 2001
From: renoseven <dev@renoseven.net>
Date: Thu, 9 May 2024 18:49:29 +0800
Subject: [PATCH 14/21] upatch-build: fix 'file detection cause build failure'
 issue

  File relation detection does not work as we expected sometimes.
We changed the way to parse file relations from parsing dwarf info
to reading symlink, which is generated by hijacker.

  This method would list all generated symlinks after each build,
finding the file it points to and create a file map. Because of build results
are same, we can use the symlink path as a KEY to find the relationship
between patched objects and original objects, which is the previous step for
comparing difference of each build.

  Sometimes, the directory of generated objects may be different than source
directory. Thus, we provide a new argument '--object-dir', which would be
same as '--source-dir' by default.

Signed-off-by: renoseven <dev@renoseven.net>
---
 upatch-build/src/args.rs          |  19 ++++-
 upatch-build/src/build_root.rs    |   8 +-
 upatch-build/src/compiler.rs      |   4 +-
 upatch-build/src/dwarf/mod.rs     |  22 +----
 upatch-build/src/file_relation.rs | 133 +++++++++++++++++-------------
 upatch-build/src/main.rs          |  33 ++++----
 upatch-build/src/project.rs       |   2 +-
 upatch-build/src/rpc/remote.rs    |   2 +-
 8 files changed, 119 insertions(+), 104 deletions(-)

diff --git a/upatch-build/src/args.rs b/upatch-build/src/args.rs
index 9f5fa15..c3b6f48 100644
--- a/upatch-build/src/args.rs
+++ b/upatch-build/src/args.rs
@@ -23,6 +23,8 @@ use super::{CLI_ABOUT, CLI_NAME, CLI_VERSION};
 
 const DEFAULT_WORK_DIR: &str = "/var/run/syscare";
 const DEFAULT_BUILD_ROOT: &str = "./upatch";
+const DEFAULT_ELF_DIR: &str = "";
+const DEFAULT_OBJECT_DIR: &str = "";
 const DEFAULT_CMD: &str = "";
 const DEFAULT_COMPILERS: &str = "cc";
 const DEFAULT_OUTPUT_DIR: &str = ".";
@@ -66,10 +68,14 @@ pub struct Arguments {
     #[clap(short, long, multiple = true, required = true)]
     pub debuginfo: Vec<PathBuf>,
 
-    /// Specify the directory of searching elf [default: <SOURCE_DIR>]
-    #[clap(long, default_value = "", hide_default_value = true)]
+    /// Specify the directory for searching elf [default: <SOURCE_DIR>]
+    #[clap(long, default_value = DEFAULT_ELF_DIR, hide_default_value = true)]
     pub elf_dir: PathBuf,
 
+    /// Specify the directory for searching object [default: <SOURCE_DIR>]
+    #[clap(long, default_value = DEFAULT_OBJECT_DIR, hide_default_value = true)]
+    pub object_dir: PathBuf,
+
     /// Specify elf's relative path relate to 'elf' or absolute patch list
     #[clap(long, multiple = true, required = true)]
     pub elf: Vec<PathBuf>,
@@ -111,6 +117,10 @@ impl Arguments {
             false => fs::normalize(&args.elf_dir)?,
             true => args.source_dir.clone(),
         };
+        args.object_dir = match args.object_dir.as_os_str().is_empty() {
+            false => fs::normalize(&args.object_dir)?,
+            true => args.source_dir.clone(),
+        };
 
         for elf_path in &mut args.elf {
             *elf_path = args.elf_dir.join(&elf_path);
@@ -124,6 +134,7 @@ impl Arguments {
         self.build_root = fs::normalize(self.build_root)?;
         self.source_dir = fs::normalize(&self.source_dir)?;
         self.elf_dir = fs::normalize(&self.elf_dir)?;
+        self.object_dir = fs::normalize(&self.object_dir)?;
         self.output_dir = fs::normalize(&self.output_dir)?;
 
         for debuginfo in &mut self.debuginfo {
@@ -149,6 +160,10 @@ impl Arguments {
             self.elf_dir.is_dir(),
             format!("Cannot find elf directory {}", self.elf_dir.display())
         );
+        ensure!(
+            self.object_dir.is_dir(),
+            format!("Cannot find object directory {}", self.object_dir.display())
+        );
         for debuginfo in &self.debuginfo {
             ensure!(
                 debuginfo.is_file(),
diff --git a/upatch-build/src/build_root.rs b/upatch-build/src/build_root.rs
index 0d703a7..556231d 100644
--- a/upatch-build/src/build_root.rs
+++ b/upatch-build/src/build_root.rs
@@ -21,7 +21,7 @@ pub struct BuildRoot {
     pub path: PathBuf,
     pub original_dir: PathBuf,
     pub patched_dir: PathBuf,
-    pub output_dir: PathBuf,
+    pub temp_dir: PathBuf,
     pub log_file: PathBuf,
 }
 
@@ -30,19 +30,19 @@ impl BuildRoot {
         let path = path.as_ref().to_path_buf();
         let original_dir = path.join("original");
         let patched_dir = path.join("patched");
-        let output_dir = path.join("output");
+        let temp_dir = path.join("temp");
         let log_file = path.join("build.log");
 
         fs::create_dir_all(&path)?;
         fs::create_dir_all(&original_dir)?;
         fs::create_dir_all(&patched_dir)?;
-        fs::create_dir_all(&output_dir)?;
+        fs::create_dir_all(&temp_dir)?;
 
         Ok(Self {
             path,
             original_dir,
             patched_dir,
-            output_dir,
+            temp_dir,
             log_file,
         })
     }
diff --git a/upatch-build/src/compiler.rs b/upatch-build/src/compiler.rs
index 9c282e2..7778f0b 100644
--- a/upatch-build/src/compiler.rs
+++ b/upatch-build/src/compiler.rs
@@ -169,7 +169,7 @@ impl Compiler {
 }
 
 impl Compiler {
-    pub fn parse<I, P, Q>(compilers: I, build_dir: Q) -> Result<Vec<Compiler>>
+    pub fn parse<I, P, Q>(compilers: I, temp_dir: Q) -> Result<Vec<Compiler>>
     where
         I: IntoIterator<Item = P>,
         P: AsRef<Path>,
@@ -183,7 +183,7 @@ impl Compiler {
                 .file_name()
                 .context("Failed to parse compiler name")?;
 
-            let output_dir = build_dir.as_ref().join(compiler_name);
+            let output_dir = temp_dir.as_ref().join(compiler_name);
             fs::create_dir_all(&output_dir)?;
 
             debug!("- Checking {}", compiler.display());
diff --git a/upatch-build/src/dwarf/mod.rs b/upatch-build/src/dwarf/mod.rs
index aded143..35f359e 100644
--- a/upatch-build/src/dwarf/mod.rs
+++ b/upatch-build/src/dwarf/mod.rs
@@ -22,7 +22,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result};
 use gimli::{
     constants, Attribute, AttributeValue, EndianSlice, Endianity, Reader, RunTimeEndian, SectionId,
 };
@@ -65,26 +65,6 @@ impl Dwarf {
         Self::get_files(&object, endian)
     }
 
-    pub fn parse_source_file<P: AsRef<Path>>(object: P) -> Result<PathBuf> {
-        let source_files = Dwarf::parse(&object)
-            .with_context(|| format!("Failed to read dwarf of {}", object.as_ref().display()))?
-            .into_iter()
-            .filter_map(|dwarf| {
-                let file_path = dwarf.compile_dir.join(&dwarf.file_name);
-                match file_path.exists() {
-                    true => Some(file_path),
-                    false => None,
-                }
-            })
-            .collect::<IndexSet<_>>();
-
-        match source_files.len() {
-            1 => Ok(source_files[0].clone()),
-            0 => bail!("Object does not contain source file"),
-            _ => bail!("Object contains to too many source files"),
-        }
-    }
-
     pub fn parse_compiler_versions<P: AsRef<Path>>(object: P) -> Result<IndexSet<OsString>> {
         let compiler_versions = Dwarf::parse(&object)
             .with_context(|| format!("Failed to read dwarf of {}", object.as_ref().display()))?
diff --git a/upatch-build/src/file_relation.rs b/upatch-build/src/file_relation.rs
index ef67af1..5a58d04 100644
--- a/upatch-build/src/file_relation.rs
+++ b/upatch-build/src/file_relation.rs
@@ -21,7 +21,6 @@ use indexmap::{IndexMap, IndexSet};
 use syscare_common::{ffi::OsStrExt, fs};
 
 use super::{
-    dwarf::Dwarf,
     elf::{check_elf, read},
     pattern_path::glob,
 };
@@ -29,25 +28,32 @@ use super::{
 const UPATCH_SYM_PREFIX: &str = ".upatch_";
 const OBJECT_EXTENSION: &str = "o";
 
+/*
+ * The task of this class is to find out:
+ * 1. relationship between binary and debuginfo
+ * 2. relationship between output binaries and objects
+ * 3. relationship between original objects and patched objects
+ */
+
 #[derive(Debug)]
 pub struct FileRelation {
-    binary_debug_map: IndexMap<PathBuf, PathBuf>, // Binary -> Debuginfo
-    source_origin_map: IndexMap<PathBuf, PathBuf>, // Source file -> Original object
-    binary_patched_map: IndexMap<PathBuf, IndexSet<PathBuf>>, // Binary -> Patched objects
-    patched_original_map: IndexMap<PathBuf, PathBuf>, // Patched object -> Original object
+    debuginfo_map: IndexMap<PathBuf, PathBuf>, // Binary -> Debuginfo
+    symlink_map: IndexMap<PathBuf, PathBuf>,   // Symlink object -> Orignal object
+    patch_objects_map: IndexMap<PathBuf, IndexSet<PathBuf>>, // Binary -> Patched objects
+    original_object_map: IndexMap<PathBuf, PathBuf>, // Patched object -> Original object
 }
 
 impl FileRelation {
     pub fn new() -> Self {
         Self {
-            binary_debug_map: IndexMap::new(),
-            binary_patched_map: IndexMap::new(),
-            source_origin_map: IndexMap::new(),
-            patched_original_map: IndexMap::new(),
+            debuginfo_map: IndexMap::new(),
+            symlink_map: IndexMap::new(),
+            patch_objects_map: IndexMap::new(),
+            original_object_map: IndexMap::new(),
         }
     }
 
-    pub fn collect_outputs<I, J, P, Q>(&mut self, binaries: I, debuginfos: J) -> Result<()>
+    pub fn collect_debuginfo<I, J, P, Q>(&mut self, binaries: I, debuginfos: J) -> Result<()>
     where
         I: IntoIterator<Item = P>,
         J: IntoIterator<Item = Q>,
@@ -61,86 +67,99 @@ impl FileRelation {
             let binary = Self::find_binary_file(binary)?;
             let debuginfo = debuginfo.as_ref().to_path_buf();
 
-            self.binary_debug_map.insert(binary, debuginfo);
+            self.debuginfo_map.insert(binary, debuginfo);
         }
 
         Ok(())
     }
 
-    pub fn collect_original_build<P: AsRef<Path>>(&mut self, object_dir: P) -> Result<()> {
-        for (binary, _) in &self.binary_debug_map {
-            let upatch_ids = Self::parse_upatch_ids(binary)
-                .with_context(|| format!("Failed to parse upatch id of {}", binary.display()))?;
-
-            for upatch_id in upatch_ids {
-                let original_object = Self::find_object_file(&object_dir, &upatch_id)
-                    .with_context(|| {
-                        format!("Failed to find object of {}", upatch_id.to_string_lossy())
-                    })?;
-                let source_file =
-                    Dwarf::parse_source_file(&original_object).with_context(|| {
-                        format!(
-                            "Failed to parse source file of {}",
-                            original_object.display()
-                        )
-                    })?;
-
-                self.source_origin_map.insert(source_file, original_object);
+    pub fn collect_original_build<P, Q>(&mut self, object_dir: P, expected_dir: Q) -> Result<()>
+    where
+        P: AsRef<Path>,
+        Q: AsRef<Path>,
+    {
+        let symlinks = fs::list_symlinks(&object_dir, fs::TraverseOptions { recursive: true })?;
+        for symlink in symlinks {
+            let object = fs::read_link(&symlink)?;
+            if !object.starts_with(expected_dir.as_ref().as_os_str()) {
+                continue;
             }
+            self.symlink_map.insert(symlink, object);
         }
+        ensure!(
+            !self.symlink_map.is_empty(),
+            "Cannot find any valid objects in {}",
+            object_dir.as_ref().display()
+        );
 
         Ok(())
     }
 
-    pub fn collect_patched_build<P: AsRef<Path>>(&mut self, object_dir: P) -> Result<()> {
-        for (binary, _) in &self.binary_debug_map {
+    pub fn collect_patched_build<P, Q>(&mut self, object_dir: P, expected_dir: Q) -> Result<()>
+    where
+        P: AsRef<Path>,
+        Q: AsRef<Path>,
+    {
+        let mut symlink_map = IndexMap::new();
+        let symlinks = fs::list_symlinks(&object_dir, fs::TraverseOptions { recursive: true })?;
+        for symlink in symlinks {
+            let object = fs::read_link(&symlink)?;
+            if !object.starts_with(expected_dir.as_ref().as_os_str()) {
+                continue;
+            }
+            symlink_map.insert(object, symlink);
+        }
+        ensure!(
+            !self.symlink_map.is_empty(),
+            "Cannot find any valid objects in {}",
+            object_dir.as_ref().display()
+        );
+
+        for (binary, _) in &self.debuginfo_map {
+            let mut objects = IndexSet::new();
+
             let upatch_ids = Self::parse_upatch_ids(binary)
                 .with_context(|| format!("Failed to parse upatch id of {}", binary.display()))?;
-
-            let mut patched_objects = IndexSet::new();
             for upatch_id in upatch_ids {
-                let patched_object =
-                    Self::find_object_file(&object_dir, &upatch_id).with_context(|| {
-                        format!("Failed to find object of {}", upatch_id.to_string_lossy())
+                let patched_object = Self::get_object_file(&expected_dir, &upatch_id)
+                    .with_context(|| {
+                        format!("Failed to get object of {}", upatch_id.to_string_lossy())
                     })?;
-                let source_file = Dwarf::parse_source_file(&patched_object).with_context(|| {
-                    format!(
-                        "Failed to parse source file of {}",
-                        patched_object.display()
-                    )
-                })?;
-                let original_object =
-                    self.source_origin_map.get(&source_file).with_context(|| {
+                let original_object = symlink_map
+                    .get(&patched_object)
+                    .and_then(|path| self.symlink_map.get(path))
+                    .with_context(|| {
                         format!(
-                            "Failed to find original object of {}",
+                            "failed to find original object of {}",
                             patched_object.display()
                         )
-                    })?;
+                    })
+                    .cloned()?;
 
-                patched_objects.insert(patched_object.clone());
-                self.patched_original_map
-                    .insert(patched_object, original_object.to_path_buf());
+                // Update object relations
+                self.original_object_map
+                    .insert(patched_object.to_owned(), original_object);
+                objects.insert(patched_object);
             }
-
-            self.binary_patched_map
-                .insert(binary.to_path_buf(), patched_objects);
+            self.patch_objects_map.insert(binary.to_owned(), objects);
         }
+        self.symlink_map.clear(); // clear useless records
 
         Ok(())
     }
 
     pub fn get_files(&self) -> impl IntoIterator<Item = (&Path, &Path)> {
-        self.binary_debug_map
+        self.debuginfo_map
             .iter()
             .map(|(binary, debuginfo)| (binary.as_path(), debuginfo.as_path()))
     }
 
     pub fn get_patched_objects<P: AsRef<Path>>(&self, binary: P) -> Option<&IndexSet<PathBuf>> {
-        self.binary_patched_map.get(binary.as_ref())
+        self.patch_objects_map.get(binary.as_ref())
     }
 
     pub fn get_original_object<P: AsRef<Path>>(&self, object: P) -> Option<&Path> {
-        self.patched_original_map
+        self.original_object_map
             .get(object.as_ref())
             .map(|p| p.as_path())
     }
@@ -166,7 +185,7 @@ impl FileRelation {
         }
     }
 
-    fn find_object_file<P, S>(object_dir: P, upatch_id: S) -> Result<PathBuf>
+    fn get_object_file<P, S>(object_dir: P, upatch_id: S) -> Result<PathBuf>
     where
         P: AsRef<Path>,
         S: AsRef<OsStr>,
diff --git a/upatch-build/src/main.rs b/upatch-build/src/main.rs
index 99de581..b5c14a8 100644
--- a/upatch-build/src/main.rs
+++ b/upatch-build/src/main.rs
@@ -49,7 +49,7 @@ use file_relation::FileRelation;
 use hijacker::Hijacker;
 use project::Project;
 
-const CLI_NAME: &str = "syscare build";
+const CLI_NAME: &str = "upatch build";
 const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
 const CLI_ABOUT: &str = env!("CARGO_PKG_DESCRIPTION");
 const CLI_UMASK: u32 = 0o022;
@@ -59,7 +59,7 @@ const LOG_FILE_NAME: &str = "build";
 struct BuildInfo {
     files: FileRelation,
     linker: PathBuf,
-    build_dir: PathBuf,
+    temp_dir: PathBuf,
     output_dir: PathBuf,
     verbose: bool,
 }
@@ -256,11 +256,11 @@ impl UpatchBuild {
         let debuginfo_name = debuginfo
             .file_name()
             .context("Failed to parse debuginfo name")?;
-        let output_dir = build_info.build_dir.join(binary_name);
-        let new_debuginfo = output_dir.join(debuginfo_name);
+        let temp_dir = build_info.temp_dir.join(binary_name);
+        let new_debuginfo = temp_dir.join(debuginfo_name);
 
         debug!("- Preparing to build patch");
-        fs::create_dir_all(&output_dir)?;
+        fs::create_dir_all(&temp_dir)?;
         fs::copy(debuginfo, &new_debuginfo)?;
         fs::set_permissions(&new_debuginfo, Permissions::from_mode(0o644))?;
 
@@ -288,7 +288,7 @@ impl UpatchBuild {
                 original_object,
                 patched_object,
                 &new_debuginfo,
-                &output_dir,
+                &temp_dir,
                 build_info.verbose,
             )
             .with_context(|| format!("Failed to create diff objects for {}", binary.display()))?;
@@ -296,7 +296,7 @@ impl UpatchBuild {
 
         debug!("- Collecting changes");
         let mut changed_objects = fs::list_files_by_ext(
-            &output_dir,
+            &temp_dir,
             OBJECT_EXTENSION,
             fs::TraverseOptions { recursive: false },
         )?;
@@ -306,7 +306,7 @@ impl UpatchBuild {
         }
 
         debug!("- Creating patch notes");
-        let notes_object = output_dir.join(NOTES_OBJECT_NAME);
+        let notes_object = temp_dir.join(NOTES_OBJECT_NAME);
         Self::create_note(&new_debuginfo, &notes_object).context("Failed to create patch notes")?;
         changed_objects.push(notes_object);
 
@@ -332,10 +332,10 @@ impl UpatchBuild {
             };
             let output_file = build_info.output_dir.join(&patch_name);
 
-            info!("Generating patch {}", patch_name.to_string_lossy());
+            info!("Generating patch for '{}'", patch_name.to_string_lossy());
             self.build_patch(&build_info, binary, debuginfo, &output_file)
                 .with_context(|| {
-                    format!("Failed to build patch {}", patch_name.to_string_lossy())
+                    format!("Failed to build patch '{}'", patch_name.to_string_lossy())
                 })?;
         }
 
@@ -346,11 +346,12 @@ impl UpatchBuild {
         let work_dir = self.args.work_dir.as_path();
         let name = self.args.name.as_os_str();
         let output_dir = self.args.output_dir.as_path();
+        let object_dir = self.args.object_dir.as_path();
         let binaries = self.args.elf.as_slice();
         let debuginfos = self.args.debuginfo.as_slice();
         let verbose = self.args.verbose;
 
-        let build_dir = self.build_root.output_dir.as_path();
+        let temp_dir = self.build_root.temp_dir.as_path();
         let original_dir = self.build_root.original_dir.as_path();
         let patched_dir = self.build_root.patched_dir.as_path();
 
@@ -359,7 +360,7 @@ impl UpatchBuild {
         info!("==============================");
         info!("Checking compiler(s)");
 
-        let compilers = Compiler::parse(&self.args.compiler, build_dir)?;
+        let compilers = Compiler::parse(&self.args.compiler, temp_dir)?;
         let linker = compilers
             .iter()
             .map(|c| c.linker.clone())
@@ -406,8 +407,8 @@ impl UpatchBuild {
             .with_context(|| format!("Failed to build {}", project))?;
 
         info!("Collecting file relations");
-        files.collect_outputs(binaries, debuginfos)?;
-        files.collect_original_build(original_dir)?;
+        files.collect_debuginfo(binaries, debuginfos)?;
+        files.collect_original_build(object_dir, original_dir)?;
 
         info!("Preparing {}", project);
         project
@@ -425,7 +426,7 @@ impl UpatchBuild {
             .with_context(|| format!("Failed to rebuild {}", project))?;
 
         info!("Collecting file relations");
-        files.collect_patched_build(patched_dir)?;
+        files.collect_patched_build(object_dir, patched_dir)?;
 
         // Unhack compilers
         drop(hijacker);
@@ -433,7 +434,7 @@ impl UpatchBuild {
         let build_info = BuildInfo {
             linker,
             files,
-            build_dir: build_dir.to_path_buf(),
+            temp_dir: temp_dir.to_path_buf(),
             output_dir: output_dir.to_path_buf(),
             verbose,
         };
diff --git a/upatch-build/src/project.rs b/upatch-build/src/project.rs
index b2f8230..b36c26b 100644
--- a/upatch-build/src/project.rs
+++ b/upatch-build/src/project.rs
@@ -46,7 +46,7 @@ pub struct Project<'a> {
 impl<'a> Project<'a> {
     pub fn new(args: &'a Arguments, build_root: &'a BuildRoot) -> Self {
         let root_dir = args.source_dir.as_path();
-        let build_dir = build_root.output_dir.as_path();
+        let build_dir = build_root.temp_dir.as_path();
         let original_dir = build_root.original_dir.as_path();
         let patched_dir = build_root.patched_dir.as_path();
 
diff --git a/upatch-build/src/rpc/remote.rs b/upatch-build/src/rpc/remote.rs
index a34f713..9927ece 100644
--- a/upatch-build/src/rpc/remote.rs
+++ b/upatch-build/src/rpc/remote.rs
@@ -56,7 +56,7 @@ impl RpcRemote {
         match error {
             Error::Transport(e) => {
                 anyhow!(
-                    "Cannot connect to syscare daemon at unix://{}, {}",
+                    "Cannot connect to upatch daemon at unix://{}, {}",
                     self.socket.display(),
                     e.source()
                         .map(|e| e.to_string())
-- 
2.41.0

