diff --git a/0131-project-update-Cargo.lock.patch b/0131-project-update-Cargo.lock.patch new file mode 100644 index 0000000000000000000000000000000000000000..2d28dda466c4274fe19230e327844653b248ba73 --- /dev/null +++ b/0131-project-update-Cargo.lock.patch @@ -0,0 +1,49 @@ +From 83762aa78f04dfa1842e239d4dc9a1d70d746938 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 13 Feb 2025 11:38:02 +0800 +Subject: [PATCH] project: update Cargo.lock + +Signed-off-by: renoseven +--- + Cargo.lock | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/Cargo.lock b/Cargo.lock +index b4798de..b1598cd 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -679,6 +679,31 @@ dependencies = [ + "autocfg", + ] + ++[[package]] ++name = "metadata-generator" ++version = "1.2.2" ++dependencies = [ ++ "anyhow", ++ "clap", ++ "flexi_logger", ++ "lazy_static", ++ "log", ++ "syscare-abi", ++ "syscare-common", ++] ++ ++[[package]] ++name = "metadata-viewer" ++version = "1.2.2" ++dependencies = [ ++ "anyhow", ++ "clap", ++ "flexi_logger", ++ "log", ++ "syscare-abi", ++ "syscare-common", ++] ++ + [[package]] + name = "miniz_oxide" + version = "0.5.4" +-- +2.43.0 + diff --git a/0132-upatch-build-rename-keep-line-macros-to-override-lin.patch b/0132-upatch-build-rename-keep-line-macros-to-override-lin.patch new file mode 100644 index 0000000000000000000000000000000000000000..a6fd2146294fe1878bc7b8516f1016d769b539a9 --- /dev/null +++ b/0132-upatch-build-rename-keep-line-macros-to-override-lin.patch @@ -0,0 +1,53 @@ +From f7f2d2299609a9f5a11976161ad8e1c7bb73ff94 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 14 Jun 2025 18:40:44 +0800 +Subject: [PATCH] upatch-build: rename '--keep-line-macros' to + '--override-line-macros' + +Signed-off-by: renoseven +--- + upatch-build/src/args.rs | 4 ++-- + upatch-build/src/main.rs | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/upatch-build/src/args.rs b/upatch-build/src/args.rs +index f840095..e3fc45b 100644 +--- a/upatch-build/src/args.rs ++++ b/upatch-build/src/args.rs +@@ -95,9 +95,9 @@ pub struct Arguments { + #[clap(short, long, default_value = DEFAULT_OUTPUT_DIR)] + pub output_dir: PathBuf, + +- /// Keep line macro unchanged ++ /// Override line macros to a fixed value + #[clap(long)] +- pub keep_line_macros: bool, ++ pub override_line_macros: bool, + + /// Skip compiler version check (not recommended) + #[clap(long)] +diff --git a/upatch-build/src/main.rs b/upatch-build/src/main.rs +index 29d68f6..2ae47fe 100644 +--- a/upatch-build/src/main.rs ++++ b/upatch-build/src/main.rs +@@ -352,7 +352,7 @@ impl UpatchBuild { + .with_context(|| format!("Failed to clean {}", project))?; + } + +- if !self.args.keep_line_macros { ++ if self.args.override_line_macros { + info!("Overriding line macros"); + project + .override_line_macros() +@@ -395,7 +395,7 @@ impl UpatchBuild { + .apply_patches() + .with_context(|| format!("Failed to patch {}", project))?; + +- if !self.args.keep_line_macros { ++ if self.args.override_line_macros { + info!("Overriding line macros"); + project + .override_line_macros() +-- +2.43.0 + diff --git a/0133-syscare-build-rename-keep-line-macros-to-override-li.patch b/0133-syscare-build-rename-keep-line-macros-to-override-li.patch new file mode 100644 index 0000000000000000000000000000000000000000..50a52687a66ec12ffc46bd46200aa838182b7196 --- /dev/null +++ b/0133-syscare-build-rename-keep-line-macros-to-override-li.patch @@ -0,0 +1,92 @@ +From 3aac147a5b0a2ae44257474520fc3119f030eefc Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 14 Jun 2025 18:40:11 +0800 +Subject: [PATCH] syscare-build: rename '--keep-line-macros' to + '--override-line-macros' + +Signed-off-by: renoseven +--- + syscare-build/src/args.rs | 4 ++-- + syscare-build/src/build_params.rs | 2 +- + syscare-build/src/main.rs | 2 +- + syscare-build/src/patch/user_patch/upatch_builder.rs | 8 ++++---- + 4 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/syscare-build/src/args.rs b/syscare-build/src/args.rs +index 4a51715..cba93f5 100644 +--- a/syscare-build/src/args.rs ++++ b/syscare-build/src/args.rs +@@ -97,9 +97,9 @@ pub struct Arguments { + #[clap(short, long, default_value = &DEFAULT_BUILD_JOBS)] + pub jobs: usize, + +- /// Keep line macro unchanged (userspace patch only) ++ /// Override line macros to a fixed value (userspace patch only) + #[clap(long)] +- pub keep_line_macros: bool, ++ pub override_line_macros: bool, + + /// Skip compiler version check (not recommended) + #[clap(long)] +diff --git a/syscare-build/src/build_params.rs b/syscare-build/src/build_params.rs +index 29dd960..40d17dc 100644 +--- a/syscare-build/src/build_params.rs ++++ b/syscare-build/src/build_params.rs +@@ -39,7 +39,7 @@ pub struct BuildParameters { + pub patch_description: String, + pub patch_files: Vec, + pub jobs: usize, +- pub keep_line_macros: bool, ++ pub override_line_macros: bool, + pub skip_compiler_check: bool, + pub skip_cleanup: bool, + pub verbose: bool, +diff --git a/syscare-build/src/main.rs b/syscare-build/src/main.rs +index 51f9050..27754af 100644 +--- a/syscare-build/src/main.rs ++++ b/syscare-build/src/main.rs +@@ -305,7 +305,7 @@ impl SyscareBuild { + patch_type, + patch_files, + jobs: self.args.jobs, +- keep_line_macros: self.args.keep_line_macros, ++ override_line_macros: self.args.override_line_macros, + skip_compiler_check: self.args.skip_compiler_check, + skip_cleanup: self.args.skip_cleanup, + verbose: self.args.verbose, +diff --git a/syscare-build/src/patch/user_patch/upatch_builder.rs b/syscare-build/src/patch/user_patch/upatch_builder.rs +index a69373c..b54d11f 100644 +--- a/syscare-build/src/patch/user_patch/upatch_builder.rs ++++ b/syscare-build/src/patch/user_patch/upatch_builder.rs +@@ -54,7 +54,7 @@ struct UBuildParameters { + patch_target: PackageInfo, + patch_description: String, + patch_files: Vec, +- keep_line_macros: bool, ++ override_line_macros: bool, + skip_compiler_check: bool, + verbose: bool, + } +@@ -182,7 +182,7 @@ impl UserPatchBuilder { + patch_target: build_params.build_entry.target_pkg.to_owned(), + patch_description: build_params.patch_description.to_owned(), + patch_files: build_params.patch_files.to_owned(), +- keep_line_macros: build_params.keep_line_macros, ++ override_line_macros: build_params.override_line_macros, + skip_compiler_check: build_params.skip_compiler_check, + verbose: build_params.verbose, + }; +@@ -225,8 +225,8 @@ impl UserPatchBuilder { + .arg("--output-dir") + .arg(&ubuild_params.patch_output_dir); + +- if ubuild_params.keep_line_macros { +- cmd_args.arg("--keep-line-macros"); ++ if ubuild_params.override_line_macros { ++ cmd_args.arg("--override-line-macros"); + } + if ubuild_params.skip_compiler_check { + cmd_args.arg("--skip-compiler-check"); +-- +2.43.0 + diff --git a/0134-common-update-component.patch b/0134-common-update-component.patch new file mode 100644 index 0000000000000000000000000000000000000000..669e46aa1e6e7c602267b0759b0510bffd03b8d8 --- /dev/null +++ b/0134-common-update-component.patch @@ -0,0 +1,3784 @@ +From ba2be6caf6062d753e91fb4e4f762effdad928a2 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 29 May 2025 18:12:32 +0800 +Subject: [PATCH] common: update component + +1. rewrite common::os +2. rewrite common::process +3. rewrite common::util::digest +4. remove common::io +5. move fs::fs_impl::xattr to fs::xattr +6. impl fs::flock::flock_exists() +7. prevent file creation in fs::mmap() +8. support kernel module management + +Signed-off-by: renoseven +--- + syscare-common/Cargo.toml | 3 +- + syscare-common/src/fs/flock.rs | 130 ++++-- + syscare-common/src/fs/fs_impl.rs | 97 +---- + syscare-common/src/fs/glob.rs | 4 +- + syscare-common/src/fs/mmap.rs | 87 ++-- + syscare-common/src/fs/mod.rs | 2 + + syscare-common/src/fs/xattr.rs | 97 +++++ + syscare-common/src/io/mod.rs | 19 - + syscare-common/src/io/os_lines.rs | 78 ---- + syscare-common/src/io/select.rs | 77 ---- + syscare-common/src/lib.rs | 1 - + syscare-common/src/os/cpu.rs | 46 +-- + syscare-common/src/os/kernel.rs | 161 +++++++- + syscare-common/src/os/mod.rs | 2 - + syscare-common/src/os/platform.rs | 82 ++-- + syscare-common/src/os/proc_maps.rs | 90 ---- + syscare-common/src/os/proc_mounts.rs | 160 -------- + syscare-common/src/os/process.rs | 68 +--- + syscare-common/src/os/selinux.rs | 331 +++++++++++---- + syscare-common/src/os/umask.rs | 80 ++-- + syscare-common/src/os/user.rs | 122 +++--- + syscare-common/src/process/args.rs | 55 --- + syscare-common/src/process/child.rs | 68 ++-- + syscare-common/src/process/command.rs | 133 ------ + syscare-common/src/process/envs.rs | 68 ---- + syscare-common/src/process/mod.rs | 563 +++++++++++++++++++------- + syscare-common/src/process/output.rs | 179 ++++++++ + syscare-common/src/process/stdio.rs | 179 -------- + syscare-common/src/util/digest.rs | 216 +++++++++- + 29 files changed, 1702 insertions(+), 1496 deletions(-) + create mode 100644 syscare-common/src/fs/xattr.rs + delete mode 100644 syscare-common/src/io/mod.rs + delete mode 100644 syscare-common/src/io/os_lines.rs + delete mode 100644 syscare-common/src/io/select.rs + delete mode 100644 syscare-common/src/os/proc_maps.rs + delete mode 100644 syscare-common/src/os/proc_mounts.rs + delete mode 100644 syscare-common/src/process/args.rs + delete mode 100644 syscare-common/src/process/command.rs + delete mode 100644 syscare-common/src/process/envs.rs + create mode 100644 syscare-common/src/process/output.rs + delete mode 100644 syscare-common/src/process/stdio.rs + +diff --git a/syscare-common/Cargo.toml b/syscare-common/Cargo.toml +index c559631..f0b6446 100644 +--- a/syscare-common/Cargo.toml ++++ b/syscare-common/Cargo.toml +@@ -10,10 +10,11 @@ build = "build.rs" + + [dependencies] + anyhow = { version = "1.0" } +-lazy_static = { version = "1.4" } + log = { version = "0.4" } + memmap2 = { version = "0.9" } + nix = { version = "0.26" } ++num_cpus = { version = "1.14" } ++object = { version = "0.29" } + regex = { version = "1.7" } + sha2 = { version = "0.10" } + serde = { version = "1.0", features = ["derive"] } +diff --git a/syscare-common/src/fs/flock.rs b/syscare-common/src/fs/flock.rs +index a1ca6ca..931a9c7 100644 +--- a/syscare-common/src/fs/flock.rs ++++ b/syscare-common/src/fs/flock.rs +@@ -16,7 +16,7 @@ use std::{ + fs::File, + io::Result, + ops::{Deref, DerefMut}, +- os::unix::io::AsRawFd, ++ os::{fd::RawFd, unix::io::AsRawFd}, + path::Path, + }; + +@@ -36,23 +36,15 @@ pub struct FileLock { + } + + impl FileLock { +- fn new>(file_path: P, kind: FileLockType) -> Result { +- let file_path = file_path.as_ref(); +- let flock = Self { +- file: if file_path.exists() { +- File::open(file_path)? +- } else { +- File::create(file_path)? +- }, +- }; +- flock.acquire(kind)?; ++ fn new(file_path: &Path, kind: FileLockType) -> Result { ++ let file = File::open(file_path)?; ++ Self::acquire_flock(file.as_raw_fd(), kind)?; + +- Ok(flock) ++ Ok(Self { file }) + } + + #[inline] +- fn acquire(&self, kind: FileLockType) -> Result<()> { +- let fd = self.file.as_raw_fd(); ++ fn acquire_flock(fd: RawFd, kind: FileLockType) -> Result<()> { + let arg = match kind { + FileLockType::Shared => fcntl::FlockArg::LockShared, + FileLockType::Exclusive => fcntl::FlockArg::LockExclusive, +@@ -65,10 +57,8 @@ impl FileLock { + } + + #[inline] +- fn release(&self) { +- let fd = self.file.as_raw_fd(); +- let arg = fcntl::FlockArg::Unlock; +- fcntl::flock(fd, arg).expect("Failed to release file lock"); ++ fn release_flock(fd: RawFd) { ++ fcntl::flock(fd, fcntl::FlockArg::Unlock).expect("Failed to release file lock"); + } + } + +@@ -88,48 +78,110 @@ impl DerefMut for FileLock { + + impl Drop for FileLock { + fn drop(&mut self) { +- self.release(); ++ Self::release_flock(self.file.as_raw_fd()); + } + } + ++pub fn flock_exists>(file_path: P, kind: FileLockType) -> Result { ++ FileLock::new(file_path.as_ref(), kind) ++} ++ + pub fn flock>(file_path: P, kind: FileLockType) -> Result { +- FileLock::new(file_path, kind) ++ let file_path = file_path.as_ref(); ++ if !file_path.exists() { ++ File::create(file_path)?; ++ } ++ self::flock_exists(file_path, kind) + } + + #[test] + fn test() -> anyhow::Result<()> { +- use anyhow::{ensure, Context}; +- ++ use anyhow::{anyhow, ensure}; + use std::fs; + + let file_path = std::env::temp_dir().join("flock_test"); +- fs::remove_file(&file_path)?; +- +- println!("Testing shared flock on {}...", file_path.display()); +- let shared_lock = self::flock(&file_path, FileLockType::SharedNonBlock) +- .context("Failed to create shared flock")?; +- let shared_lock1 = self::flock(&file_path, FileLockType::SharedNonBlock) +- .context("Failed to create shared flock")?; ++ let non_exist_file = std::env::temp_dir().join("flock_test_non_exist"); ++ ++ fs::remove_file(&file_path).ok(); ++ fs::remove_file(&non_exist_file).ok(); ++ ++ fs::write(&file_path, "flock_test")?; ++ ++ println!("Testing fs::flock_exists()..."); ++ println!("- Shared flock '{}'...", file_path.display()); ++ let shared_lock = ++ self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { ++ anyhow!( ++ "Failed to create shared flock '{}', {}", ++ file_path.display(), ++ e ++ ) ++ })?; ++ let shared_lock1 = ++ self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { ++ anyhow!( ++ "Failed to create shared flock '{}', {}", ++ file_path.display(), ++ e ++ ) ++ })?; + ensure!( +- self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), +- "Exclusive flock should be failed" ++ self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), ++ "Exclusive flock '{}' should be failed", ++ file_path.display() + ); + drop(shared_lock); + drop(shared_lock1); + +- println!("Testing exclusive flock on {}...", file_path.display()); +- let exclusive_lock = self::flock(&file_path, FileLockType::ExclusiveNonBlock) +- .context("Failed to create exclusive flock")?; ++ println!("- Exclusive flock '{}'...", file_path.display()); ++ let exclusive_lock = ++ self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).map_err(|e| { ++ anyhow!( ++ "Failed to create exclusive flock '{}', {}", ++ file_path.display(), ++ e ++ ) ++ })?; + ensure!( +- self::flock(&file_path, FileLockType::SharedNonBlock).is_err(), +- "Shared flock should be failed" ++ self::flock_exists(&file_path, FileLockType::SharedNonBlock).is_err(), ++ "Shared flock '{}' should be failed", ++ file_path.display() + ); + ensure!( +- self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), +- "Exclusive flock should be failed" ++ self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), ++ "Exclusive flock '{}' should be failed", ++ file_path.display() + ); +- + drop(exclusive_lock); + ++ println!("- Non-exist flock '{}'...", non_exist_file.display()); ++ ensure!( ++ self::flock_exists(&non_exist_file, FileLockType::SharedNonBlock).is_err(), ++ "Shared flock '{}' should be failed", ++ non_exist_file.display() ++ ); ++ ensure!( ++ self::flock_exists(&non_exist_file, FileLockType::ExclusiveNonBlock).is_err(), ++ "Exclusive flock '{}' should be failed", ++ non_exist_file.display() ++ ); ++ ++ println!("Testing fs::flock()..."); ++ println!("- Non-exist flock '{}'...", non_exist_file.display()); ++ let _ = self::flock(&non_exist_file, FileLockType::SharedNonBlock).map_err(|e| { ++ anyhow!( ++ "Failed to create shared flock '{}', {}", ++ file_path.display(), ++ e ++ ) ++ })?; ++ let _ = self::flock(&non_exist_file, FileLockType::ExclusiveNonBlock).map_err(|e| { ++ anyhow!( ++ "Failed to create exclusive flock '{}', {}", ++ file_path.display(), ++ e ++ ) ++ })?; ++ + Ok(()) + } +diff --git a/syscare-common/src/fs/fs_impl.rs b/syscare-common/src/fs/fs_impl.rs +index f5393f8..a048804 100644 +--- a/syscare-common/src/fs/fs_impl.rs ++++ b/syscare-common/src/fs/fs_impl.rs +@@ -14,15 +14,14 @@ + + use std::{ + env, +- ffi::{CStr, OsStr, OsString}, ++ ffi::{OsStr, OsString}, + fs::{File, FileType, Metadata, Permissions, ReadDir}, + io, +- os::{raw::c_void, unix::fs::PermissionsExt}, ++ os::unix::fs::PermissionsExt, + path::{Component, Path, PathBuf}, +- ptr::null_mut, + }; + +-use crate::ffi::{CStrExt, OsStrExt}; ++use crate::ffi::OsStrExt; + + trait RewriteError { + fn rewrite_err(self, err_msg: String) -> Self; +@@ -196,96 +195,6 @@ pub fn file_ext>(path: P) -> OsString { + .unwrap_or_default() + } + +-pub fn getxattr(path: P, name: S) -> std::io::Result +-where +- P: AsRef, +- S: AsRef, +-{ +- let file_path = path.as_ref().to_cstring()?; +- let xattr_name = name.as_ref().to_cstring()?; +- +- /* +- * SAFETY: +- * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. +- * In our implementation, the buffer is checked properly, so that would be safe. +- */ +- let mut ret = +- unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), null_mut(), 0) }; +- if ret == -1 { +- return Err(std::io::Error::last_os_error()).rewrite_err(format!( +- "Cannot get path {} xattr {}", +- file_path.to_string_lossy(), +- xattr_name.to_string_lossy() +- )); +- } +- +- let mut buf = vec![0; ret.unsigned_abs()]; +- let value_ptr = buf.as_mut_ptr().cast::(); +- +- /* +- * SAFETY: +- * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. +- * In our implementation, the buffer is checked properly, so that would be safe. +- */ +- ret = unsafe { +- nix::libc::getxattr( +- file_path.as_ptr(), +- xattr_name.as_ptr(), +- value_ptr, +- buf.len(), +- ) +- }; +- if ret == -1 { +- return Err(std::io::Error::last_os_error()).rewrite_err(format!( +- "Cannot get path {} xattr {}", +- file_path.to_string_lossy(), +- xattr_name.to_string_lossy(), +- )); +- } +- +- let value = CStr::from_bytes_with_nul(&buf[0..ret.unsigned_abs()]) +- .unwrap_or_default() +- .to_os_string(); +- +- Ok(value) +-} +- +-pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> +-where +- P: AsRef, +- S: AsRef, +- T: AsRef, +-{ +- let file_path = path.as_ref().to_cstring()?; +- let xattr_name = name.as_ref().to_cstring()?; +- let xattr_value = value.as_ref().to_cstring()?; +- let size = xattr_value.to_bytes_with_nul().len(); +- +- /* +- * SAFETY: +- * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. +- * In our implementation, the buffer is checked properly, so that would be safe. +- */ +- let ret = unsafe { +- nix::libc::setxattr( +- file_path.as_ptr(), +- xattr_name.as_ptr(), +- xattr_value.as_ptr().cast::(), +- size, +- 0, +- ) +- }; +- if ret == -1 { +- return Err(std::io::Error::last_os_error()).rewrite_err(format!( +- "Cannot set {} xattr {}", +- file_path.to_string_lossy(), +- xattr_name.to_string_lossy() +- )); +- } +- +- Ok(()) +-} +- + pub fn normalize>(path: P) -> io::Result { + let mut new_path = PathBuf::new(); + +diff --git a/syscare-common/src/fs/glob.rs b/syscare-common/src/fs/glob.rs +index 8165343..9c115f4 100644 +--- a/syscare-common/src/fs/glob.rs ++++ b/syscare-common/src/fs/glob.rs +@@ -69,7 +69,7 @@ impl Glob { + } + + fn match_component(&mut self, path: PathBuf, index: usize) -> Result> { +- let last_index = self.components.len() - 1; ++ let last_index = self.components.len().saturating_sub(1); + + for dir_entry in fs::read_dir(&path)? { + let next_path = dir_entry?.path(); +@@ -126,7 +126,7 @@ impl Iterator for Glob { + + fn next(&mut self) -> Option { + while let Some((mut curr_path, mut index)) = self.stack.pop() { +- let last_index = self.components.len() - 1; ++ let last_index = self.components.len().saturating_sub(1); + + // iterate all of components over matching path + while index <= last_index { +diff --git a/syscare-common/src/fs/mmap.rs b/syscare-common/src/fs/mmap.rs +index b65f170..07c9a17 100644 +--- a/syscare-common/src/fs/mmap.rs ++++ b/syscare-common/src/fs/mmap.rs +@@ -12,36 +12,18 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{io::Result, ops::Deref, os::fd::AsRawFd, path::Path}; ++use std::{ops::Deref, os::unix::io::AsRawFd, path::Path}; + + use memmap2::{Advice, Mmap, MmapOptions}; + +-use super::flock::*; ++use super::flock; + + #[derive(Debug)] + pub struct FileMmap { +- _lock: FileLock, ++ _lock: flock::FileLock, + mmap: Mmap, + } + +-impl FileMmap { +- fn new>(file_path: P) -> Result { +- /* +- * SAFETY: +- * All file-backed memory map constructors are marked unsafe because of the +- * potential for Undefined Behavior (UB) using the map if the underlying file +- * is subsequently modified, in or out of process. +- * Our implementation uses shared file lock to avoid cross-process file access. +- * This mapped area would be safe. +- */ +- let lock = flock(file_path, FileLockType::Shared)?; +- let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; +- mmap.advise(Advice::Random)?; +- +- Ok(Self { _lock: lock, mmap }) +- } +-} +- + impl Deref for FileMmap { + type Target = [u8]; + +@@ -51,27 +33,58 @@ impl Deref for FileMmap { + } + + pub fn mmap>(file_path: P) -> std::io::Result { +- FileMmap::new(file_path) ++ /* ++ * SAFETY: ++ * All file-backed memory map constructors are marked unsafe because of the ++ * potential for Undefined Behavior (UB) using the map if the underlying file ++ * is subsequently modified, in or out of process. ++ * Our implementation uses shared file lock to avoid cross-process file access. ++ * This mapped area would be safe. ++ */ ++ let lock = flock::flock_exists(file_path, flock::FileLockType::Shared)?; ++ let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; ++ mmap.advise(Advice::Random)?; ++ ++ Ok(FileMmap { _lock: lock, mmap }) + } + +-#[test] +-fn test() -> anyhow::Result<()> { +- use anyhow::Context; ++#[cfg(test)] ++mod test { ++ use std::fs; ++ ++ use super::*; + +- const FILE_PATH: &str = "/etc/os-release"; +- const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; +- const PROC_FS_PATH: &str = "/proc/version"; ++ #[test] ++ fn mmap_file() -> std::io::Result<()> { ++ let file_path = std::env::temp_dir().join("mmap_test"); ++ fs::remove_file(&file_path).ok(); ++ fs::write(&file_path, "mmap_test")?; + +- println!("Testing FileMmap..."); +- let orig_file = +- std::fs::read(FILE_PATH).with_context(|| format!("Failed to open file {}", FILE_PATH))?; +- let map_file = +- self::mmap(FILE_PATH).with_context(|| format!("Failed to mmap file {}", FILE_PATH))?; ++ let orig_file = fs::read(&file_path)?; ++ let map_file = self::mmap(&file_path)?; ++ assert_eq!(orig_file, map_file.as_ref()); + +- let _ = self::mmap(SYS_FS_PATH).expect_err("Sysfs cannot not be mapped"); +- let _ = self::mmap(PROC_FS_PATH).expect_err("Procfs cannot not be mapped"); ++ fs::remove_file(&file_path)?; ++ Ok(()) ++ } + +- assert_eq!(orig_file, map_file.as_ref()); ++ #[test] ++ fn mmap_non_exists_file() { ++ const NON_EXISTS_FILE: &str = "/non_exists_file"; ++ fs::remove_file(NON_EXISTS_FILE).ok(); + +- Ok(()) ++ assert!(self::mmap(NON_EXISTS_FILE).is_err(),); ++ } ++ ++ #[test] ++ fn mmap_proc_file() { ++ const PROC_FS_PATH: &str = "/proc/version"; ++ assert!(self::mmap(PROC_FS_PATH).is_err(),); ++ } ++ ++ #[test] ++ fn mmap_sys_file() { ++ const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; ++ assert!(self::mmap(SYS_FS_PATH).is_err(),); ++ } + } +diff --git a/syscare-common/src/fs/mod.rs b/syscare-common/src/fs/mod.rs +index 3526915..b6d2d04 100644 +--- a/syscare-common/src/fs/mod.rs ++++ b/syscare-common/src/fs/mod.rs +@@ -16,8 +16,10 @@ mod flock; + mod fs_impl; + mod glob; + mod mmap; ++mod xattr; + + pub use flock::*; + pub use fs_impl::*; + pub use glob::*; + pub use mmap::*; ++pub use xattr::*; +diff --git a/syscare-common/src/fs/xattr.rs b/syscare-common/src/fs/xattr.rs +new file mode 100644 +index 0000000..237c70b +--- /dev/null ++++ b/syscare-common/src/fs/xattr.rs +@@ -0,0 +1,97 @@ ++// SPDX-License-Identifier: Mulan PSL v2 ++/* ++ * Copyright (c) 2024 Huawei Technologies Co., Ltd. ++ * syscare-common is licensed under Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, ++ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, ++ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use std::{ ++ ffi::{c_void, CString, OsStr, OsString}, ++ os::unix::ffi::{OsStrExt, OsStringExt}, ++ path::Path, ++ ptr, ++}; ++ ++pub fn getxattr(path: P, name: S) -> std::io::Result ++where ++ P: AsRef, ++ S: AsRef, ++{ ++ let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; ++ let xattr_name = CString::new(name.as_ref().as_bytes())?; ++ ++ /* ++ * SAFETY: ++ * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. ++ * In our implementation, the buffer is checked properly, so that would be safe. ++ */ ++ let buf_size = ++ unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), ptr::null_mut(), 0) }; ++ if buf_size == -1 { ++ return Err(std::io::Error::last_os_error()); ++ } ++ ++ let mut buf = vec![0; buf_size.unsigned_abs()]; ++ let value_ptr = buf.as_mut_ptr().cast::(); ++ ++ /* ++ * SAFETY: ++ * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. ++ * In our implementation, the buffer is checked properly, so that would be safe. ++ */ ++ let bytes_read = unsafe { ++ nix::libc::getxattr( ++ file_path.as_ptr(), ++ xattr_name.as_ptr(), ++ value_ptr, ++ buf.len(), ++ ) ++ }; ++ if bytes_read == -1 { ++ return Err(std::io::Error::last_os_error()); ++ } ++ if buf.last() == Some(&0) { ++ buf.pop(); ++ } ++ ++ Ok(OsString::from_vec(buf)) ++} ++ ++pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> ++where ++ P: AsRef, ++ S: AsRef, ++ T: AsRef, ++{ ++ let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; ++ let xattr_name = CString::new(name.as_ref().as_bytes())?; ++ let xattr_value = CString::new(value.as_ref().as_bytes())?; ++ let size = xattr_value.to_bytes_with_nul().len(); ++ ++ /* ++ * SAFETY: ++ * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. ++ * In our implementation, the buffer is checked properly, so that would be safe. ++ */ ++ let ret = unsafe { ++ nix::libc::setxattr( ++ file_path.as_ptr(), ++ xattr_name.as_ptr(), ++ xattr_value.as_ptr().cast::(), ++ size, ++ 0, ++ ) ++ }; ++ if ret == -1 { ++ return Err(std::io::Error::last_os_error()); ++ } ++ ++ Ok(()) ++} +diff --git a/syscare-common/src/io/mod.rs b/syscare-common/src/io/mod.rs +deleted file mode 100644 +index a36f750..0000000 +--- a/syscare-common/src/io/mod.rs ++++ /dev/null +@@ -1,19 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-mod os_lines; +-mod select; +- +-pub use os_lines::*; +-pub use select::*; +diff --git a/syscare-common/src/io/os_lines.rs b/syscare-common/src/io/os_lines.rs +deleted file mode 100644 +index 08ae0ce..0000000 +--- a/syscare-common/src/io/os_lines.rs ++++ /dev/null +@@ -1,78 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ffi::OsString, io::BufRead, os::unix::prelude::OsStringExt}; +- +-pub struct OsLines { +- buf: R, +-} +- +-impl Iterator for OsLines { +- type Item = std::io::Result; +- +- fn next(&mut self) -> Option { +- const CHAR_LF: [u8; 1] = [b'\n']; +- const CHAR_CR: [u8; 1] = [b'\r']; +- +- let mut buf = Vec::new(); +- match self.buf.read_until(CHAR_LF[0], &mut buf) { +- Ok(0) => None, +- Ok(_) => { +- // Drop "\n" or "\r\n" on the buf tail +- if buf.ends_with(&CHAR_LF) { +- buf.pop(); +- if buf.ends_with(&CHAR_CR) { +- buf.pop(); +- } +- } +- buf.shrink_to_fit(); +- Some(Ok(OsString::from_vec(buf))) +- } +- Err(_) => Some(self.buf.read_to_end(&mut buf).map(|_| { +- buf.shrink_to_fit(); +- OsString::from_vec(buf) +- })), +- } +- } +-} +- +-pub trait BufReadOsLines: BufRead + Sized { +- fn os_lines(self) -> OsLines { +- OsLines { buf: self } +- } +-} +- +-impl BufReadOsLines for R {} +- +-#[test] +-fn test() { +- use crate::fs; +- use std::io::BufReader; +- +- let buf_reader = +- BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); +- let lines = buf_reader.lines(); +- for str in lines.flatten() { +- println!("{}", str); +- assert!(!str.is_empty()); +- } +- +- let buf_reader = +- BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); +- let os_lines = buf_reader.os_lines(); +- for str in os_lines.flatten() { +- println!("{}", str.to_string_lossy()); +- assert!(!str.is_empty()); +- } +-} +diff --git a/syscare-common/src/io/select.rs b/syscare-common/src/io/select.rs +deleted file mode 100644 +index 5d9400f..0000000 +--- a/syscare-common/src/io/select.rs ++++ /dev/null +@@ -1,77 +0,0 @@ +-use std::{ +- os::unix::io::{AsRawFd, RawFd}, +- time::Duration, +-}; +- +-use anyhow::Result; +-use nix::sys::{ +- select::{select, FdSet}, +- time::TimeVal, +-}; +- +-pub enum SelectResult { +- Readable(RawFd), +- Writable(RawFd), +- Error(RawFd), +-} +- +-pub struct Select { +- fd_set: FdSet, +- readfds: FdSet, +- writefds: FdSet, +- errorfds: FdSet, +- timeout: Option, +-} +- +-impl Select { +- pub fn new(fds: I) -> Self +- where +- I: IntoIterator, +- F: AsRawFd, +- { +- Self::with_timeout(fds, None) +- } +- +- pub fn with_timeout(fds: I, duration: Option) -> Self +- where +- I: IntoIterator, +- F: AsRawFd, +- { +- let mut fd_set = FdSet::new(); +- for fd in fds { +- fd_set.insert(fd.as_raw_fd()); +- } +- let readfds = FdSet::new(); +- let writefds = FdSet::new(); +- let errorfds = FdSet::new(); +- let timeout = duration.map(|t| TimeVal::new(t.as_secs() as i64, t.subsec_micros() as i64)); +- +- Self { +- fd_set, +- readfds, +- writefds, +- errorfds, +- timeout, +- } +- } +- +- pub fn select(&mut self) -> Result + '_> { +- self.readfds = self.fd_set; +- self.writefds = self.fd_set; +- self.errorfds = self.fd_set; +- +- select( +- None, +- &mut self.readfds, +- &mut self.writefds, +- &mut self.errorfds, +- &mut self.timeout, +- )?; +- +- let rd_fds = self.readfds.fds(None).map(SelectResult::Readable); +- let wr_fds = self.writefds.fds(None).map(SelectResult::Writable); +- let err_fds = self.errorfds.fds(None).map(SelectResult::Error); +- +- Ok(rd_fds.chain(wr_fds).chain(err_fds)) +- } +-} +diff --git a/syscare-common/src/lib.rs b/syscare-common/src/lib.rs +index a3188e6..b568a2c 100644 +--- a/syscare-common/src/lib.rs ++++ b/syscare-common/src/lib.rs +@@ -14,7 +14,6 @@ + + pub mod ffi; + pub mod fs; +-pub mod io; + mod macros; + pub mod os; + pub mod os_str; +diff --git a/syscare-common/src/os/cpu.rs b/syscare-common/src/os/cpu.rs +index ead7eee..9d39bca 100644 +--- a/syscare-common/src/os/cpu.rs ++++ b/syscare-common/src/os/cpu.rs +@@ -12,39 +12,35 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsStr; ++use std::ffi::OsString; + +-use lazy_static::lazy_static; +- +-use nix::{ +- sched::{sched_getaffinity, CpuSet}, +- unistd::getpid, +-}; ++use num_cpus; + + use super::platform; + +-pub fn arch() -> &'static OsStr { ++pub fn arch() -> OsString { + platform::arch() + } + + pub fn num() -> usize { +- lazy_static! { +- static ref CPU_NUM: usize = { +- let cpu_set = sched_getaffinity(getpid()).unwrap_or_default(); +- let mut cpu_count = 0; +- for i in 0..CpuSet::count() { +- if cpu_set.is_set(i).unwrap_or_default() { +- cpu_count += 1; +- } +- } +- cpu_count +- }; +- } +- *CPU_NUM ++ num_cpus::get() + } + +-#[test] +-fn test() { +- println!("arch: {}", arch().to_string_lossy()); +- println!("num: {}", num()) ++#[cfg(test)] ++mod test { ++ use super::*; ++ ++ #[test] ++ fn test_arch() { ++ let arch = self::arch(); ++ println!("cpu arch: {}", arch.to_string_lossy()); ++ assert!(!arch.is_empty()); ++ } ++ ++ #[test] ++ fn test_num() { ++ let num = self::num(); ++ println!("cpu num: {}", num); ++ assert_ne!(num, 0); ++ } + } +diff --git a/syscare-common/src/os/kernel.rs b/syscare-common/src/os/kernel.rs +index a29e663..c355167 100644 +--- a/syscare-common/src/os/kernel.rs ++++ b/syscare-common/src/os/kernel.rs +@@ -12,10 +12,165 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsStr; ++// SPDX-License-Identifier: Mulan PSL v2 ++/* ++ * Copyright (c) 2024 Huawei Technologies Co., Ltd. ++ * syscare-common is licensed under Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, ++ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, ++ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use std::{ ++ collections::HashSet, ++ ffi::{CStr, CString, OsStr, OsString}, ++ fs::File, ++ os::unix::ffi::OsStrExt, ++ path::{Path, PathBuf}, ++}; ++ ++use log::{error, trace}; ++use nix::{errno::Errno, kmod}; ++use object::{Object, ObjectSection}; ++ ++use super::{platform, selinux}; ++use crate::fs; ++ ++const SYS_MODULE_DIR: &str = "/sys/module"; ++ ++pub type KernelModuleInfo = ModuleInfo; ++pub type KernelModuleGuard = ModuleGuard; + +-use super::platform; ++#[derive(Debug)] ++pub struct ModuleInfo { ++ pub name: OsString, ++ pub depends: HashSet, ++ pub module_path: PathBuf, ++} ++ ++#[derive(Debug)] ++pub struct ModuleGuard(ModuleInfo); ++ ++impl Drop for ModuleGuard { ++ fn drop(&mut self) { ++ if !self.0.module_path.exists() { ++ return; ++ } ++ if let Err(e) = self::remove_module(&self.0.name) { ++ error!( ++ "Failed to remove kernel module '{}', {}", ++ self.0.name.to_string_lossy(), ++ e.to_string().to_lowercase() ++ ); ++ } ++ } ++} + +-pub fn version() -> &'static OsStr { ++pub fn version() -> OsString { + platform::release() + } ++ ++pub fn list_modules() -> std::io::Result> { ++ const LIST_OPTIONS: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; ++ ++ let modules = fs::list_dirs(SYS_MODULE_DIR, LIST_OPTIONS)? ++ .into_iter() ++ .filter_map(|path| path.file_name().map(OsStr::to_os_string)) ++ .collect(); ++ Ok(modules) ++} ++ ++pub fn relable_module_file>(file_path: P) -> std::io::Result<()> { ++ const KMOD_FILE_TYPE: &str = "modules_object_t"; ++ ++ let file_path = file_path.as_ref(); ++ let mut context = selinux::get_security_context(file_path)?; ++ if context.get_type() == KMOD_FILE_TYPE { ++ return Ok(()); ++ } ++ ++ context.set_type(KMOD_FILE_TYPE)?; ++ selinux::set_security_context(file_path, &context)?; ++ ++ Ok(()) ++} ++ ++pub fn module_info>(file_path: P) -> std::io::Result { ++ const MODINFO_SECTION_NAME: &str = ".modinfo"; ++ const MODINFO_NAME_PREFIX: &[u8] = b"name="; ++ const MODINFO_DEPENDS_PREFIX: &[u8] = b"depends="; ++ ++ let file_path = file_path.as_ref().to_path_buf(); ++ let mmap = fs::mmap(&file_path)?; ++ let file = object::File::parse(&*mmap).map_err(|_| Errno::ENOEXEC)?; ++ if file.format() != object::BinaryFormat::Elf { ++ return Err(std::io::Error::from(Errno::ENOEXEC)); ++ } ++ ++ let data = file ++ .section_by_name(MODINFO_SECTION_NAME) ++ .and_then(|section| section.data().ok()) ++ .ok_or(Errno::ENOEXEC)?; ++ let name = data ++ .split(|b| *b == b'\0') ++ .find_map(|entry| entry.strip_prefix(MODINFO_NAME_PREFIX)) ++ .map(|slice| OsStr::from_bytes(slice).to_os_string()) ++ .ok_or(Errno::ENOEXEC)?; ++ let depends = data ++ .split(|b| *b == b'\0') ++ .find_map(|entry| entry.strip_prefix(MODINFO_DEPENDS_PREFIX)) ++ .unwrap_or_default() ++ .split(|b| *b == b',') ++ .filter(|b| !b.is_empty()) ++ .map(|b| OsStr::from_bytes(b).to_os_string()) ++ .collect(); ++ let module_path = Path::new(SYS_MODULE_DIR).join(&name); ++ ++ let kmod = ModuleInfo { ++ name, ++ depends, ++ module_path, ++ }; ++ Ok(kmod) ++} ++ ++pub fn insert_module>(file_path: P) -> std::io::Result<()> { ++ const PARAM_VALUES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; ++ ++ let file_path = file_path.as_ref(); ++ trace!("Inserting kernel module '{}'...", file_path.display()); ++ ++ let file = File::open(file_path)?; ++ kmod::finit_module(&file, PARAM_VALUES, kmod::ModuleInitFlags::empty())?; ++ ++ Ok(()) ++} ++ ++pub fn insert_module_guarded>(file_path: P) -> std::io::Result { ++ let file_path = file_path.as_ref(); ++ ++ let modinfo = self::module_info(file_path)?; ++ if !modinfo.module_path.exists() { ++ self::insert_module(file_path)?; ++ } ++ ++ Ok(ModuleGuard(modinfo)) ++} ++ ++pub fn remove_module>(module_name: S) -> std::io::Result<()> { ++ let module_name = module_name.as_ref(); ++ trace!( ++ "Removing kernel module '{}'...", ++ module_name.to_string_lossy() ++ ); ++ ++ let name = CString::new(module_name.as_bytes())?; ++ kmod::delete_module(&name, kmod::DeleteModuleFlags::O_NONBLOCK)?; ++ ++ Ok(()) ++} +diff --git a/syscare-common/src/os/mod.rs b/syscare-common/src/os/mod.rs +index 6a93a20..9d33010 100644 +--- a/syscare-common/src/os/mod.rs ++++ b/syscare-common/src/os/mod.rs +@@ -15,8 +15,6 @@ + pub mod cpu; + pub mod kernel; + pub mod platform; +-pub mod proc_maps; +-pub mod proc_mounts; + pub mod process; + pub mod selinux; + pub mod umask; +diff --git a/syscare-common/src/os/platform.rs b/syscare-common/src/os/platform.rs +index 8d38b44..178ecd4 100644 +--- a/syscare-common/src/os/platform.rs ++++ b/syscare-common/src/os/platform.rs +@@ -12,59 +12,71 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsStr; ++use std::ffi::OsString; + +-use lazy_static::lazy_static; +-use nix::sys::utsname::{uname, UtsName}; ++use nix::sys::utsname; + + #[inline(always)] +-fn info() -> &'static UtsName { +- lazy_static! { +- static ref PLATFORM_INFO: UtsName = uname().expect("Failed to get uname"); +- } +- &PLATFORM_INFO ++fn sysinfo() -> utsname::UtsName { ++ utsname::uname().expect("Failed to get system infomation") + } + +-pub fn hostname() -> &'static OsStr { +- info().nodename() ++pub fn sysname() -> OsString { ++ self::sysinfo().sysname().to_os_string() + } + +-pub fn sysname() -> &'static OsStr { +- info().sysname() ++pub fn hostname() -> OsString { ++ self::sysinfo().nodename().to_os_string() + } + +-pub fn release() -> &'static OsStr { +- info().release() ++pub fn release() -> OsString { ++ self::sysinfo().release().to_os_string() + } + +-pub fn version() -> &'static OsStr { +- info().version() ++pub fn version() -> OsString { ++ self::sysinfo().version().to_os_string() + } + +-pub fn arch() -> &'static OsStr { +- info().machine() ++pub fn arch() -> OsString { ++ self::sysinfo().machine().to_os_string() + } + +-#[test] +-fn test() { +- let sysname = sysname(); +- let hostname = hostname(); +- let release = release(); +- let version = version(); +- let arch = arch(); ++#[cfg(test)] ++mod test { ++ use super::*; + +- println!("sysname: {}", sysname.to_string_lossy()); +- assert!(!sysname.is_empty()); ++ #[test] ++ fn test_sysname() { ++ let sysname = self::sysname(); ++ println!("sysname: {}", sysname.to_string_lossy()); ++ assert!(!sysname.is_empty()); ++ } + +- println!("hostname: {}", hostname.to_string_lossy()); +- assert!(!hostname.is_empty()); ++ #[test] ++ fn test_hostname() { ++ let hostname = self::hostname(); ++ println!("hostname: {}", hostname.to_string_lossy()); ++ assert!(!hostname.is_empty()); ++ } + +- println!("release: {}", release.to_string_lossy()); +- assert!(!release.is_empty()); ++ #[test] ++ fn test_release() { ++ let release = self::release(); ++ println!("release: {}", release.to_string_lossy()); ++ assert!(!release.is_empty()); ++ } + +- println!("version: {}", version.to_string_lossy()); +- assert!(!version.is_empty()); ++ #[test] ++ fn test_version() { ++ let version = self::version(); ++ println!("version: {}", version.to_string_lossy()); ++ assert!(!version.is_empty()); ++ } + +- println!("arch: {}", arch.to_string_lossy()); +- assert!(!arch.is_empty()); ++ #[test] ++ fn test_arch() { ++ let arch = self::arch(); ++ println!("arch: {}", arch.to_string_lossy()); ++ assert!(!arch.is_empty()); ++ } + } +diff --git a/syscare-common/src/os/proc_maps.rs b/syscare-common/src/os/proc_maps.rs +deleted file mode 100644 +index 2855a4a..0000000 +--- a/syscare-common/src/os/proc_maps.rs ++++ /dev/null +@@ -1,90 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{convert::TryFrom, ffi::OsString, fs::File, io::BufReader}; +- +-use anyhow::{ensure, Result}; +- +-use crate::{ +- ffi::OsStrExt, +- fs, +- io::{BufReadOsLines, OsLines}, +-}; +- +-#[derive(Debug)] +-pub struct ProcMap { +- pub address: OsString, +- pub permission: OsString, +- pub offset: OsString, +- pub dev: OsString, +- pub inode: OsString, +- pub path_name: OsString, +-} +- +-impl TryFrom for ProcMap { +- type Error = anyhow::Error; +- +- fn try_from(value: OsString) -> std::result::Result { +- const MAP_FIELD_NUM: usize = 6; +- +- let fields = value.split_whitespace().collect::>(); +- ensure!( +- fields.len() == MAP_FIELD_NUM, +- "Failed to parse process mapping" +- ); +- +- Ok(Self { +- address: fields[0].to_owned(), +- permission: fields[1].to_owned(), +- offset: fields[2].to_owned(), +- dev: fields[3].to_owned(), +- inode: fields[4].to_owned(), +- path_name: fields[5].to_owned(), +- }) +- } +-} +- +-pub struct ProcMaps { +- lines: OsLines>, +-} +- +-impl ProcMaps { +- pub fn new(pid: i32) -> Result { +- let file_path = format!("/proc/{}/maps", pid); +- let lines = BufReader::new(fs::open_file(file_path)?).os_lines(); +- +- Ok(Self { lines }) +- } +-} +- +-impl Iterator for ProcMaps { +- type Item = ProcMap; +- +- fn next(&mut self) -> Option { +- self.lines +- .next() +- .and_then(Result::ok) +- .map(ProcMap::try_from) +- .and_then(Result::ok) +- } +-} +- +-#[test] +-fn test() { +- use super::process; +- +- for map in ProcMaps::new(process::id()).expect("Failed to read proc maps") { +- println!("{:#?}", map); +- } +-} +diff --git a/syscare-common/src/os/proc_mounts.rs b/syscare-common/src/os/proc_mounts.rs +deleted file mode 100644 +index 1e720bf..0000000 +--- a/syscare-common/src/os/proc_mounts.rs ++++ /dev/null +@@ -1,160 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::ffi::{OsStr, OsString}; +-use std::fs::File; +-use std::io::BufReader; +-use std::os::unix::ffi::{OsStrExt as StdOsStrExt, OsStringExt}; +-use std::path::PathBuf; +- +-use anyhow::Result; +- +-use crate::{ +- ffi::OsStrExt, +- fs, +- io::{BufReadOsLines, OsLines}, +-}; +- +-#[derive(Debug)] +-pub struct MountInfo { +- pub mount_id: u32, +- pub parent_id: u32, +- pub device_id: OsString, +- pub root: PathBuf, +- pub mount_point: PathBuf, +- pub mount_opts: Vec, +- pub optional: Vec, +- pub filesystem: OsString, +- pub mount_source: PathBuf, +- pub super_opts: Vec, +-} +- +-struct MountInfoParser<'a> { +- data: &'a [u8], +- num: usize, +- pos: usize, +-} +- +-impl<'a> Iterator for MountInfoParser<'a> { +- type Item = &'a OsStr; +- +- fn next(&mut self) -> Option { +- const OPTION_INDEX: usize = 6; +- const NORMAL_SPLITTER: char = ' '; +- const OPTION_SPLITTER: char = '-'; +- +- let data = &self.data[self.pos..]; +- let new_str = OsStr::from_bytes(data); +- if new_str.is_empty() { +- return None; +- } +- +- for (index, char) in new_str.char_indices() { +- let pattern; +- let skip_len; +- +- match self.num { +- OPTION_INDEX => { +- pattern = OPTION_SPLITTER; +- skip_len = 2; +- } +- _ => { +- pattern = NORMAL_SPLITTER; +- skip_len = 1; +- } +- }; +- if char == pattern { +- self.num += 1; +- self.pos += index + skip_len; +- +- return Some(OsStr::from_bytes(&data[..index])); +- } +- } +- +- self.pos = self.data.len() - 1; +- Some(OsStr::from_bytes(data)) +- } +-} +- +-pub struct Mounts { +- lines: OsLines>, +-} +- +-impl Mounts { +- pub fn new() -> Result { +- const MOUNTINFO_PATH: &str = "/proc/self/mountinfo"; +- +- Ok(Self { +- lines: BufReader::new(fs::open_file(MOUNTINFO_PATH)?).os_lines(), +- }) +- } +-} +- +-impl Mounts { +- fn parse_line(str: OsString) -> Option { +- const VALUE_SPLITTER: char = ','; +- +- let str_bytes = str.into_vec(); +- let mut iter = MountInfoParser { +- data: &str_bytes, +- num: 0, +- pos: 0, +- }; +- +- Some(MountInfo { +- mount_id: iter.next()?.to_string_lossy().parse::().ok()?, +- parent_id: iter.next()?.to_string_lossy().parse::().ok()?, +- device_id: iter.next()?.to_os_string(), +- root: PathBuf::from(iter.next()?), +- mount_point: PathBuf::from(iter.next()?), +- mount_opts: iter +- .next()? +- .split(VALUE_SPLITTER) +- .map(OsStrExt::trim) +- .map(OsString::from) +- .collect::>(), +- optional: iter +- .next()? +- .split_whitespace() +- .map(OsString::from) +- .collect::>(), +- filesystem: iter.next()?.to_os_string(), +- mount_source: PathBuf::from(iter.next()?), +- super_opts: iter +- .next()? +- .split(VALUE_SPLITTER) +- .map(OsStrExt::trim) +- .map(OsString::from) +- .collect::>(), +- }) +- } +-} +- +-impl Iterator for Mounts { +- type Item = MountInfo; +- +- fn next(&mut self) -> Option { +- self.lines.next()?.ok().and_then(Self::parse_line) +- } +-} +- +-#[test] +-fn test() { +- let mount_info = Mounts::new().expect("Failed to read mount info"); +- for mount in mount_info { +- println!(); +- println!("{:#?}", mount); +- assert!(mount.mount_point.exists()) +- } +-} +diff --git a/syscare-common/src/os/process.rs b/syscare-common/src/os/process.rs +index 4569587..fc8c6cd 100644 +--- a/syscare-common/src/os/process.rs ++++ b/syscare-common/src/os/process.rs +@@ -13,72 +13,48 @@ + */ + + use std::ffi::{OsStr, OsString}; +-use std::path::{Path, PathBuf}; ++use std::path::PathBuf; + +-use lazy_static::lazy_static; +-use nix::unistd::getpid; +- +-use crate::fs; ++use nix::errno::Errno; ++use nix::unistd; + + pub fn id() -> i32 { +- lazy_static! { +- static ref PROCESS_ID: i32 = getpid().as_raw(); +- } +- *PROCESS_ID ++ unistd::getpid().as_raw() + } + +-pub fn path() -> &'static Path { +- lazy_static! { +- static ref PROCESS_PATH: PathBuf = +- std::env::current_exe().unwrap_or_else(|_| PathBuf::from("/")); +- } +- PROCESS_PATH.as_path() ++pub fn path() -> std::io::Result { ++ std::env::current_exe() + } + +-pub fn name() -> &'static OsStr { +- lazy_static! { +- static ref PROCESS_NAME: OsString = fs::file_name(path()); +- } +- PROCESS_NAME.as_os_str() ++pub fn name() -> std::io::Result { ++ self::path()? ++ .file_name() ++ .map(OsStr::to_os_string) ++ .ok_or(std::io::Error::from(Errno::EINVAL)) + } + + #[cfg(test)] +-mod tests_process { +- use crate::os::process::{id, name, path}; +- use std::process::Command; +- use std::{println, string::ToString}; +- +- fn build_commd(s: &str) -> String { +- let mut cmd = "ps -ef |grep ".to_string(); +- cmd = cmd + s + "|grep -v grep"; +- let output = Command::new("bash").arg("-c").arg(cmd).output().unwrap(); +- String::from_utf8(output.stdout).unwrap() +- } ++mod test { ++ use super::*; + + #[test] + fn test_id() { +- let process_id = id().to_string(); +- println!("This process id is {}", process_id); +- +- let sys_proc = build_commd(&process_id); +- assert!(!sys_proc.is_empty()); ++ let pid = self::id(); ++ println!("pid: {}", pid); ++ assert!(pid > 1) + } + + #[test] + fn test_path() { +- let process_path = path().display().to_string(); +- println!("This path is {:#?}", process_path); +- +- let sys_path = build_commd(&process_path); +- assert!(!sys_path.is_empty()); ++ let path = self::path().expect("Failed to get executable path"); ++ println!("path: {}", path.display()); ++ assert!(path.exists()); + } + + #[test] + fn test_name() { +- let process_name = name().to_string_lossy(); +- println!("This name is {:#?}", process_name); +- +- let sys_name = build_commd(&process_name); +- assert!(!sys_name.is_empty()); ++ let name = self::name().expect("Failed to get executable name"); ++ println!("name: {}", name.to_string_lossy()); ++ assert!(!name.is_empty()); + } + } +diff --git a/syscare-common/src/os/selinux.rs b/syscare-common/src/os/selinux.rs +index bb9864c..e1aa73e 100644 +--- a/syscare-common/src/os/selinux.rs ++++ b/syscare-common/src/os/selinux.rs +@@ -12,119 +12,304 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsString; +-use std::os::unix::ffi::OsStringExt as UnixOsStringExt; +-use std::path::Path; ++use std::{ ++ ffi::{OsStr, OsString}, ++ fmt::Debug, ++ os::unix::ffi::{OsStrExt, OsStringExt}, ++ path::Path, ++}; + +-use anyhow::{bail, ensure, Context, Result}; ++use nix::errno::Errno; + +-use crate::{concat_os, ffi::OsStrExt, fs}; ++use crate::fs; + + const SELINUX_SYS_FILE: &str = "/sys/fs/selinux/enforce"; +-const SELINUX_XATTR_NAME: &str = "security.selinux"; +-const SELINUX_XATTR_SPLITTER: &str = ":"; +-const SECURITY_XATTR_LEN: usize = 4; ++const SELINUX_STATUS_PERMISSIVE: &str = "0"; ++const SELINUX_STATUS_ENFORCING: &str = "1"; ++ ++const SECURITY_CONTEXT_XATTR_NAME: &str = "security.selinux"; ++const SECURITY_CONTEXT_SPLITER: u8 = b':'; ++const SECURITY_CONTEXT_SPLITER_COUNT: usize = 3; ++const SECURITY_CONTEXT_ATTR_COUNT: usize = SECURITY_CONTEXT_SPLITER_COUNT + 1; ++ ++const SECURITY_CONTEXT_USER_INDEX: usize = 0; ++const SECURITY_CONTEXT_ROLE_INDEX: usize = 1; ++const SECURITY_CONTEXT_TYPE_INDEX: usize = 2; ++const SECURITY_CONTEXT_LEVEL_INDEX: usize = 3; ++ ++pub type SELinuxStatus = Status; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum Status { ++ Disabled, + Permissive, + Enforcing, +- Disabled, + } + + impl std::fmt::Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- f.write_fmt(format_args!("{:?}", self)) ++ write!(f, "{:?}", self) + } + } + +-pub fn get_status() -> Result { +- if !Path::new(SELINUX_SYS_FILE).is_file() { +- return Ok(Status::Disabled); ++pub fn get_status() -> Status { ++ let value = fs::read_to_string(SELINUX_SYS_FILE).unwrap_or_default(); ++ match value.as_str() { ++ SELINUX_STATUS_PERMISSIVE => Status::Permissive, ++ SELINUX_STATUS_ENFORCING => Status::Enforcing, ++ _ => Status::Disabled, + } ++} + +- let value = OsString::from_vec(fs::read(SELINUX_SYS_FILE)?) +- .to_string_lossy() +- .parse::() +- .context("Failed to parse selinux status")?; ++pub fn set_status(value: Status) -> std::io::Result<()> { ++ let contents = match value { ++ Status::Permissive => SELINUX_STATUS_PERMISSIVE, ++ Status::Enforcing => SELINUX_STATUS_ENFORCING, ++ _ => return Err(std::io::Error::from(Errno::EINVAL)), ++ }; ++ fs::write(SELINUX_SYS_FILE, contents)?; + +- Ok(match value { +- 0 => Status::Permissive, +- 1 => Status::Enforcing, +- _ => Status::Disabled, +- }) ++ Ok(()) + } + +-pub fn set_status(value: Status) -> Result<()> { +- if (value != Status::Permissive) && (value != Status::Enforcing) { +- bail!("Status {} is invalid", value); ++#[derive(Clone, PartialEq, Eq)] ++pub struct SecurityContext(OsString); ++ ++impl SecurityContext { ++ fn get_attribute(&self, index: usize) -> &OsStr { ++ self.0 ++ .as_bytes() ++ .splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { ++ b == SECURITY_CONTEXT_SPLITER ++ }) ++ .nth(index) ++ .map(OsStr::from_bytes) ++ .expect("Unexpected security context format") + } +- fs::write( +- SELINUX_SYS_FILE, +- match value { +- Status::Enforcing => "1", +- _ => "0", +- }, +- )?; + +- Ok(()) ++ fn set_attribute>(&mut self, index: usize, value: S) -> std::io::Result<()> { ++ let value = value.as_ref().as_bytes(); ++ ++ if value.is_empty() { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ if (index != SECURITY_CONTEXT_LEVEL_INDEX) && (value.contains(&SECURITY_CONTEXT_SPLITER)) { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ let attrs = self.0.as_bytes().splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { ++ b == SECURITY_CONTEXT_SPLITER ++ }); ++ ++ let mut new_context = Vec::new(); ++ for (i, attr) in attrs.enumerate() { ++ new_context.extend_from_slice(if i != index { attr } else { value }); ++ new_context.push(SECURITY_CONTEXT_SPLITER); ++ } ++ new_context.pop(); ++ ++ self.0 = OsString::from_vec(new_context); ++ Ok(()) ++ } + } + +-#[derive(Debug, Clone, PartialEq, Eq)] +-pub struct SecurityContext { +- pub user: OsString, +- pub role: OsString, +- pub kind: OsString, +- pub level: OsString, ++impl SecurityContext { ++ pub fn parse>(value: S) -> std::io::Result { ++ let context = value.as_ref(); ++ if context.is_empty() { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ ++ let spliter_count = context ++ .as_bytes() ++ .iter() ++ .filter(|&b| *b == SECURITY_CONTEXT_SPLITER) ++ .count(); ++ if spliter_count < SECURITY_CONTEXT_SPLITER_COUNT { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ ++ Ok(Self(context.to_os_string())) ++ } ++ ++ pub fn get_user(&self) -> &OsStr { ++ self.get_attribute(SECURITY_CONTEXT_USER_INDEX) ++ } ++ ++ pub fn get_role(&self) -> &OsStr { ++ self.get_attribute(SECURITY_CONTEXT_ROLE_INDEX) ++ } ++ ++ pub fn get_type(&self) -> &OsStr { ++ self.get_attribute(SECURITY_CONTEXT_TYPE_INDEX) ++ } ++ ++ pub fn get_level(&self) -> &OsStr { ++ self.get_attribute(SECURITY_CONTEXT_LEVEL_INDEX) ++ } ++ ++ pub fn set_user>(&mut self, value: S) -> std::io::Result<()> { ++ self.set_attribute(SECURITY_CONTEXT_USER_INDEX, value) ++ } ++ ++ pub fn set_role>(&mut self, value: S) -> std::io::Result<()> { ++ self.set_attribute(SECURITY_CONTEXT_ROLE_INDEX, value) ++ } ++ ++ pub fn set_type>(&mut self, value: S) -> std::io::Result<()> { ++ self.set_attribute(SECURITY_CONTEXT_TYPE_INDEX, value) ++ } ++ ++ pub fn set_level>(&mut self, value: S) -> std::io::Result<()> { ++ self.set_attribute(SECURITY_CONTEXT_LEVEL_INDEX, value) ++ } ++ ++ pub fn as_os_str(&self) -> &OsStr { ++ &self.0 ++ } + } + +-impl AsRef for SecurityContext { +- fn as_ref(&self) -> &SecurityContext { +- self ++impl Debug for SecurityContext { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{:?}", self.0) + } + } + +-pub fn get_security_context

(file_path: P) -> Result ++impl AsRef for SecurityContext { ++ fn as_ref(&self) -> &OsStr { ++ self.as_os_str() ++ } ++} ++ ++pub fn get_security_context

(file_path: P) -> std::io::Result + where + P: AsRef, + { +- let value = fs::getxattr(file_path, SELINUX_XATTR_NAME)?; +- let data = value.split(SELINUX_XATTR_SPLITTER).collect::>(); +- ensure!( +- data.len() == SECURITY_XATTR_LEN, +- "Failed to parse selinux security context" +- ); +- +- Ok(SecurityContext { +- user: data[0].to_os_string(), +- role: data[1].to_os_string(), +- kind: data[2].to_os_string(), +- level: data[3].to_os_string(), +- }) ++ SecurityContext::parse(fs::getxattr(file_path, SECURITY_CONTEXT_XATTR_NAME)?) + } + +-pub fn set_security_context(file_path: P, value: S) -> Result<()> ++pub fn set_security_context

(file_path: P, value: &SecurityContext) -> std::io::Result<()> + where + P: AsRef, +- S: AsRef, + { +- let old_value = get_security_context(&file_path)?; +- let new_value = value.as_ref(); ++ fs::setxattr(file_path, SECURITY_CONTEXT_XATTR_NAME, value) ++} ++ ++#[cfg(test)] ++mod test { ++ use std::{fs, path::Path}; + +- if &old_value == new_value { +- return Ok(()); ++ use super::*; ++ ++ #[test] ++ fn test_selinux_status() { ++ let status = self::get_status(); ++ println!("SELinux status: {}", status); ++ ++ let sys_file = Path::new(self::SELINUX_SYS_FILE); ++ if sys_file.exists() { ++ assert!(self::get_status() == Status::Disabled); ++ } else { ++ assert!(self::get_status() == Status::Disabled); ++ } ++ assert!(self::set_status(Status::Disabled).is_err()); + } + +- let new_context = concat_os!( +- &new_value.user, +- SELINUX_XATTR_SPLITTER, +- &new_value.role, +- SELINUX_XATTR_SPLITTER, +- &new_value.kind, +- SELINUX_XATTR_SPLITTER, +- &new_value.level, +- ); +- fs::setxattr(&file_path, SELINUX_XATTR_NAME, new_context)?; ++ #[test] ++ fn test_security_context_parse() { ++ const TEST_CASES: &[&str] = &[ ++ "system_u:object_r:bin_t:s0", ++ "user_u:role_r:type_t:s0:c1,c2", ++ "user.dom:role-1:type_x:s0:c1.c2", ++ "a:b:c:d.e-f,g", ++ "a_:b_:c_:d_", ++ ]; ++ for str in TEST_CASES { ++ let result = SecurityContext::parse(str).is_ok(); ++ assert!(result, "Failed to parse security context '{}'", str); ++ } ++ } + +- Ok(()) ++ #[test] ++ fn test_security_context_get() { ++ const TEST_CASES: &[(&str, [&str; 4])] = &[ ++ ( ++ "system_u:object_r:bin_t:s0", ++ ["system_u", "object_r", "bin_t", "s0"], ++ ), ++ ( ++ "user_u:role_r:type_t:s0:c1,c2", ++ ["user_u", "role_r", "type_t", "s0:c1,c2"], ++ ), ++ ( ++ "user.dom:role-1:type_x:s0:c1.c2", ++ ["user.dom", "role-1", "type_x", "s0:c1.c2"], ++ ), ++ ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), ++ ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), ++ ]; ++ ++ for (case, attrs) in TEST_CASES { ++ let context = SecurityContext::parse(case).expect("Failed to parse security context"); ++ assert_eq!(context.get_user(), attrs[0]); ++ assert_eq!(context.get_role(), attrs[1]); ++ assert_eq!(context.get_type(), attrs[2]); ++ assert_eq!(context.get_level(), attrs[3]); ++ } ++ } ++ ++ #[test] ++ fn test_security_context_set() { ++ const DEFAULT_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; ++ const TEST_CASES: &[(&str, [&str; 4])] = &[ ++ ( ++ "system_u:object_r:bin_t:s0", ++ ["system_u", "object_r", "bin_t", "s0"], ++ ), ++ ( ++ "user_u:role_r:type_t:s0:c1,c2", ++ ["user_u", "role_r", "type_t", "s0:c1,c2"], ++ ), ++ ( ++ "user.dom:role-1:type_x:s0:c1.c2", ++ ["user.dom", "role-1", "type_x", "s0:c1.c2"], ++ ), ++ ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), ++ ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), ++ ]; ++ ++ for (result, attrs) in TEST_CASES { ++ let mut context = ++ SecurityContext::parse(DEFAULT_CONTEXT).expect("Failed to parse security context"); ++ assert!(context.set_user(attrs[0]).is_ok()); ++ assert!(context.set_role(attrs[1]).is_ok()); ++ assert!(context.set_type(attrs[2]).is_ok()); ++ assert!(context.set_level(attrs[3]).is_ok()); ++ println!("{:?}", context); ++ ++ assert_eq!(context.as_os_str(), *result); ++ } ++ } ++ ++ #[test] ++ fn test_get_set_security_context() { ++ const TEST_FILE: &str = "selinux_test"; ++ const TEST_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; ++ ++ let file_path = std::env::temp_dir().join(TEST_FILE); ++ ++ fs::remove_file(&file_path).ok(); ++ fs::write(&file_path, TEST_FILE).expect("Failed to write test file"); ++ ++ let set_context = SecurityContext::parse(TEST_CONTEXT).expect("Invalid security context"); ++ self::set_security_context(&file_path, &set_context) ++ .expect("Failed to set security context"); ++ println!("set context: {:#?}", set_context); ++ ++ let get_context = ++ self::get_security_context(&file_path).expect("Failed to get security context"); ++ println!("get context: {:#?}", get_context); ++ ++ assert_eq!(set_context, get_context); ++ fs::remove_file(&file_path).expect("Failed to remove test file"); ++ } + } +diff --git a/syscare-common/src/os/umask.rs b/syscare-common/src/os/umask.rs +index ab624cf..cb0d3ee 100644 +--- a/syscare-common/src/os/umask.rs ++++ b/syscare-common/src/os/umask.rs +@@ -12,45 +12,49 @@ + * See the Mulan PSL v2 for more details. + */ + +-use nix::sys::stat::{umask, Mode}; ++use nix::{libc::mode_t, sys::stat}; + +-pub fn set_umask(mode: u32) -> u32 { +- umask(Mode::from_bits_truncate(mode)).bits() ++pub fn set_umask(mode: mode_t) -> mode_t { ++ stat::umask(stat::Mode::from_bits_truncate(mode)).bits() + } + +-#[test] +-fn test() { +- use std::{fs, fs::File, os::unix::fs::PermissionsExt}; +- +- const FILE_PATH: &str = "/tmp/umask_test"; +- const UMASK1: u32 = 0o077; // 10600 +- const UMASK2: u32 = 0o022; // 10644 +- +- fs::remove_file(FILE_PATH).ok(); +- +- println!("Testing umask {:03o}...", UMASK1); +- set_umask(UMASK1); +- let file1 = File::create(FILE_PATH).expect("Failed to create file"); +- let perm1 = file1 +- .metadata() +- .map(|s| s.permissions()) +- .expect("Failed to read file permission"); +- +- println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); +- +- drop(file1); +- fs::remove_file(FILE_PATH).ok(); +- +- println!("Testing umask {:03o}...", UMASK2); +- set_umask(UMASK2); +- let file2 = File::create(FILE_PATH).expect("Failed to create file"); +- let perm2 = file2 +- .metadata() +- .map(|s| s.permissions()) +- .expect("Failed to read file permission"); +- +- println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); +- +- drop(file2); +- fs::remove_file(FILE_PATH).ok(); ++#[cfg(test)] ++mod test { ++ #[test] ++ fn test_set_umask() { ++ use std::{fs, fs::File, os::unix::fs::PermissionsExt}; ++ ++ let file_path = std::env::temp_dir().join("umask_test"); ++ const UMASK1: u32 = 0o077; // 10600 ++ const UMASK2: u32 = 0o022; // 10644 ++ ++ fs::remove_file(&file_path).ok(); ++ ++ println!("Testing umask {:03o}...", UMASK1); ++ super::set_umask(UMASK1); ++ let file1 = File::create(&file_path).expect("Failed to create file"); ++ let perm1 = file1 ++ .metadata() ++ .map(|s| s.permissions()) ++ .expect("Failed to read file permission"); ++ println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); ++ assert_eq!(perm1.mode() & 0o777, 0o600); ++ ++ drop(file1); ++ fs::remove_file(&file_path).expect("Failed to remove file"); ++ ++ println!("Testing umask {:03o}...", UMASK2); ++ super::set_umask(UMASK2); ++ let file2 = File::create(&file_path).expect("Failed to create file"); ++ let perm2 = file2 ++ .metadata() ++ .map(|s| s.permissions()) ++ .expect("Failed to read file permission"); ++ ++ println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); ++ assert_eq!(perm2.mode() & 0o777, 0o644); ++ ++ drop(file2); ++ fs::remove_file(&file_path).expect("Failed to remove file"); ++ } + } +diff --git a/syscare-common/src/os/user.rs b/syscare-common/src/os/user.rs +index db2e10e..1e79cdb 100644 +--- a/syscare-common/src/os/user.rs ++++ b/syscare-common/src/os/user.rs +@@ -12,80 +12,94 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{ +- ffi::{CString, OsStr}, +- path::{Path, PathBuf}, +-}; +- +-use lazy_static::lazy_static; +-use nix::unistd::{getuid, Gid, Uid, User}; +- +-use crate::ffi::CStrExt; +- +-fn info() -> &'static User { +- lazy_static! { +- static ref USER_INFO: User = User::from_uid(getuid()) +- .unwrap_or_default() +- .unwrap_or(User { +- name: String::from("root"), +- passwd: CString::default(), +- uid: Uid::from_raw(0), +- gid: Gid::from_raw(0), +- gecos: CString::default(), +- dir: PathBuf::from("/root"), +- shell: PathBuf::from("/bin/sh"), +- }); +- } +- &USER_INFO ++use std::{ffi::OsString, os::unix::ffi::OsStringExt, path::PathBuf}; ++ ++use nix::unistd; ++ ++#[inline(always)] ++fn userinfo() -> unistd::User { ++ unistd::User::from_uid(unistd::getuid()) ++ .expect("Failed to get user infomation") ++ .expect("Invalid user id") + } + +-pub fn name() -> &'static str { +- self::info().name.as_str() ++pub fn uid() -> u32 { ++ unistd::getuid().as_raw() + } + +-pub fn passwd() -> &'static OsStr { +- self::info().passwd.as_os_str() ++pub fn gid() -> u32 { ++ unistd::getgid().as_raw() + } + +-pub fn id() -> u32 { +- self::info().uid.as_raw() ++pub fn name() -> String { ++ self::userinfo().name + } + +-pub fn gid() -> u32 { +- self::info().gid.as_raw() ++pub fn passwd() -> OsString { ++ OsString::from_vec(self::userinfo().passwd.into_bytes()) + } + +-pub fn gecos() -> &'static OsStr { +- self::info().gecos.as_os_str() ++pub fn gecos() -> OsString { ++ OsString::from_vec(self::userinfo().gecos.into_bytes()) + } + +-pub fn home() -> &'static Path { +- self::info().dir.as_path() ++pub fn home() -> PathBuf { ++ self::userinfo().dir + } + +-pub fn shell() -> &'static Path { +- self::info().shell.as_path() ++pub fn shell() -> PathBuf { ++ self::userinfo().shell + } + +-#[test] +-fn test() { +- println!("name: {}", self::name()); +- assert!(!self::name().is_empty()); ++#[cfg(test)] ++mod test { ++ use super::*; + +- println!("passwd: {}", self::passwd().to_string_lossy()); +- assert!(!self::passwd().is_empty()); ++ #[test] ++ fn test_uid() { ++ let uid = self::uid(); ++ println!("uid: {}", uid); ++ assert!(uid < u32::MAX); ++ } + +- println!("id: {}", self::id()); +- assert!(id() < u32::MAX); ++ #[test] ++ fn test_gid() { ++ let gid = self::gid(); ++ println!("gid: {}", gid); ++ assert!(gid < u32::MAX); ++ } + +- println!("gid: {}", self::gid()); +- assert!(gid() < u32::MAX); ++ #[test] ++ fn test_name() { ++ let name = self::name(); ++ println!("name: {}", name); ++ assert!(!name.is_empty()); ++ } ++ ++ #[test] ++ fn test_passwd() { ++ let passwd = self::passwd(); ++ println!("passwd: {}", passwd.to_string_lossy()); ++ assert!(!passwd.is_empty()); ++ } + +- println!("gecos: {}", self::gecos().to_string_lossy()); ++ #[test] ++ fn test_gecos() { ++ let gecos = self::gecos(); ++ println!("gecos: {}", gecos.to_string_lossy()); ++ } + +- println!("home: {}", self::home().display()); +- assert!(self::home().exists()); ++ #[test] ++ fn test_home() { ++ let home = self::home(); ++ println!("home: {}", home.display()); ++ assert!(home.exists()); ++ } + +- println!("shell: {}", self::shell().display()); +- assert!(self::home().exists()); ++ #[test] ++ fn test_shell() { ++ let shell = self::shell(); ++ println!("shell: {}", shell.display()); ++ assert!(shell.exists()); ++ } + } +diff --git a/syscare-common/src/process/args.rs b/syscare-common/src/process/args.rs +deleted file mode 100644 +index 7d7e094..0000000 +--- a/syscare-common/src/process/args.rs ++++ /dev/null +@@ -1,55 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::ffi::{OsStr, OsString}; +- +-#[derive(Clone, Default)] +-pub struct CommandArgs { +- args: Vec, +-} +- +-impl CommandArgs { +- pub fn new() -> Self { +- Self { args: Vec::new() } +- } +- +- pub fn arg(&mut self, arg: S) -> &mut Self +- where +- S: AsRef, +- { +- self.args.push(arg.as_ref().to_os_string()); +- self +- } +- +- pub fn args(&mut self, args: I) -> &mut Self +- where +- I: IntoIterator, +- S: AsRef, +- { +- for arg in args { +- self.arg(arg); +- } +- self +- } +-} +- +-impl IntoIterator for CommandArgs { +- type Item = OsString; +- +- type IntoIter = std::vec::IntoIter; +- +- fn into_iter(self) -> Self::IntoIter { +- self.args.into_iter() +- } +-} +diff --git a/syscare-common/src/process/child.rs b/syscare-common/src/process/child.rs +index 0e8cf54..5379ef2 100644 +--- a/syscare-common/src/process/child.rs ++++ b/syscare-common/src/process/child.rs +@@ -13,58 +13,57 @@ + */ + + use std::{ +- ffi::OsString, +- ops::Deref, +- os::unix::process::ExitStatusExt, +- process::{Child as StdChild, ExitStatus as StdExitStatus}, +- thread::JoinHandle, ++ ffi::OsString, ops::Deref, os::unix::process::ExitStatusExt, process, thread::JoinHandle, + }; + + use anyhow::{anyhow, ensure, Context, Result}; + use log::trace; + +-use super::{Stdio, StdioLevel}; ++use super::output; + + pub struct Child { +- pub(super) id: u32, + pub(super) name: String, +- pub(super) stdio_level: StdioLevel, +- pub(super) inner: StdChild, ++ pub(super) child: process::Child, ++ pub(super) log_level: output::LogLevel, + } + + impl Child { +- fn capture_stdio(&mut self) -> Result> { +- Stdio::new( +- self.name.clone(), +- self.inner +- .stdout +- .take() +- .context("Failed to capture stdout")?, +- self.inner +- .stderr +- .take() +- .context("Failed to capture stderr")?, +- self.stdio_level, +- ) +- .capture() ++ fn redirect_outputs(&mut self) -> Result> { ++ let stdout = self ++ .child ++ .stdout ++ .take() ++ .context("Failed to capture stdout")?; ++ let stderr = self ++ .child ++ .stderr ++ .take() ++ .context("Failed to capture stderr")?; ++ let outputs = output::Outputs::new(stdout, stderr, self.log_level); ++ ++ std::thread::Builder::new() ++ .name(self.name.clone()) ++ .spawn(|| outputs.redirect()) ++ .with_context(|| format!("Failed to create thread {}", self.name)) + } + } + + impl Child { + pub fn kill(&mut self) -> Result<()> { +- self.inner ++ let id = self.child.id(); ++ self.child + .kill() +- .with_context(|| format!("Failed to kill process {} ({})", self.name, self.id)) ++ .with_context(|| format!("Failed to kill process {} ({})", self.name, id)) + } + + pub fn wait(&mut self) -> Result { ++ let id = self.child.id(); + let status = self +- .inner ++ .child + .wait() +- .with_context(|| format!("Failed to wait process {} ({})", self.name, self.id))?; +- ++ .with_context(|| format!("Failed to wait process {} ({})", self.name, id))?; + let exit_status = ExitStatus { +- id: self.id, ++ id, + name: self.name.clone(), + status, + }; +@@ -79,24 +78,25 @@ impl Child { + } + + pub fn wait_with_output(&mut self) -> Result { +- let stdio_thread = self.capture_stdio()?; ++ let thread = self.redirect_outputs()?; + let status = self.wait()?; +- let (stdout, stderr) = stdio_thread ++ let (stdout, stderr) = thread + .join() + .map_err(|_| anyhow!("Failed to join stdio thread"))?; + +- Ok(Output { ++ let output = Output { + status, + stdout, + stderr, +- }) ++ }; ++ Ok(output) + } + } + + pub struct ExitStatus { + id: u32, + name: String, +- status: StdExitStatus, ++ status: process::ExitStatus, + } + + impl ExitStatus { +diff --git a/syscare-common/src/process/command.rs b/syscare-common/src/process/command.rs +deleted file mode 100644 +index 06d3568..0000000 +--- a/syscare-common/src/process/command.rs ++++ /dev/null +@@ -1,133 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ +- ffi::OsStr, +- path::Path, +- process::{Command as StdCommand, Stdio}, +-}; +- +-use anyhow::{Context, Result}; +-use log::{trace, Level}; +- +-use super::{Child, ExitStatus, Output, StdioLevel}; +- +-pub struct Command { +- inner: StdCommand, +- stdio_level: StdioLevel, +-} +- +-impl Command { +- pub fn new>(program: S) -> Self { +- Self { +- inner: StdCommand::new(program), +- stdio_level: StdioLevel::default(), +- } +- } +- +- pub fn arg>(&mut self, arg: S) -> &mut Self { +- self.inner.arg(arg); +- self +- } +- +- pub fn args(&mut self, args: I) -> &mut Self +- where +- I: IntoIterator, +- S: AsRef, +- { +- for arg in args { +- self.arg(arg.as_ref()); +- } +- self +- } +- +- pub fn env(&mut self, key: K, val: V) -> &mut Self +- where +- K: AsRef, +- V: AsRef, +- { +- self.inner.env(key, val); +- self +- } +- +- pub fn envs(&mut self, vars: I) -> &mut Self +- where +- I: IntoIterator, +- K: AsRef, +- V: AsRef, +- { +- for (ref key, ref val) in vars { +- self.env(key, val); +- } +- self +- } +- +- pub fn env_clear(&mut self) -> &mut Self { +- self.inner.env_clear(); +- self +- } +- +- pub fn current_dir>(&mut self, dir: P) -> &mut Self { +- self.inner.current_dir(dir); +- self +- } +- +- pub fn stdin>(&mut self, cfg: T) -> &mut Self { +- self.inner.stdin(cfg); +- self +- } +- +- pub fn stdout>>(&mut self, level: T) -> &mut Self { +- self.stdio_level.stdout = level.into(); +- self +- } +- +- pub fn stderr(&mut self, level: Level) -> &mut Self { +- self.stdio_level.stderr = level.into(); +- self +- } +- +- pub fn spawn(&mut self) -> Result { +- let name = Path::new(self.inner.get_program()) +- .file_name() +- .context("Failed to get process name")? +- .to_string_lossy() +- .to_string(); +- +- trace!("Executing {:?}", self.inner); +- let child = self +- .inner +- .spawn() +- .with_context(|| format!("Failed to start {}", name))?; +- +- Ok(Child { +- id: child.id(), +- name, +- stdio_level: self.stdio_level, +- inner: child, +- }) +- } +- +- pub fn run(&mut self) -> Result { +- self.inner.stdout(Stdio::null()).stderr(Stdio::null()); +- +- self.spawn()?.wait() +- } +- +- pub fn run_with_output(&mut self) -> Result { +- self.inner.stdout(Stdio::piped()).stderr(Stdio::piped()); +- +- self.spawn()?.wait_with_output() +- } +-} +diff --git a/syscare-common/src/process/envs.rs b/syscare-common/src/process/envs.rs +deleted file mode 100644 +index 038ad60..0000000 +--- a/syscare-common/src/process/envs.rs ++++ /dev/null +@@ -1,68 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ +- collections::HashMap, +- ffi::{OsStr, OsString}, +-}; +- +-pub struct CommandEnvs { +- envs: HashMap, +-} +- +-impl CommandEnvs { +- pub fn new() -> Self { +- Self { +- envs: HashMap::new(), +- } +- } +- +- pub fn env(&mut self, key: K, value: V) -> &mut Self +- where +- K: AsRef, +- V: AsRef, +- { +- self.envs +- .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); +- self +- } +- +- pub fn envs(&mut self, vars: I) -> &mut Self +- where +- I: IntoIterator, +- K: AsRef, +- V: AsRef, +- { +- for (key, value) in vars { +- self.env(key, value); +- } +- self +- } +-} +- +-impl Default for CommandEnvs { +- fn default() -> Self { +- Self::new() +- } +-} +- +-impl IntoIterator for CommandEnvs { +- type Item = (OsString, OsString); +- +- type IntoIter = std::collections::hash_map::IntoIter; +- +- fn into_iter(self) -> Self::IntoIter { +- self.envs.into_iter() +- } +-} +diff --git a/syscare-common/src/process/mod.rs b/syscare-common/src/process/mod.rs +index 894707c..1fe472c 100644 +--- a/syscare-common/src/process/mod.rs ++++ b/syscare-common/src/process/mod.rs +@@ -12,151 +12,434 @@ + * See the Mulan PSL v2 for more details. + */ + +-mod args; ++use std::{ ++ collections::HashMap, ++ ffi::{OsStr, OsString}, ++ path::Path, ++ process, ++}; ++ ++use anyhow::{Context, Result}; ++use log::{trace, Level}; ++ + mod child; +-mod command; +-mod envs; +-mod stdio; ++mod output; ++ ++#[derive(Debug, Clone)] ++pub struct CommandArgs(Vec); ++ ++impl CommandArgs { ++ pub fn new() -> Self { ++ Self(Vec::new()) ++ } ++ ++ pub fn arg(&mut self, arg: S) -> &mut Self ++ where ++ S: AsRef, ++ { ++ self.0.push(arg.as_ref().to_os_string()); ++ self ++ } ++ ++ pub fn args(&mut self, args: I) -> &mut Self ++ where ++ I: IntoIterator, ++ S: AsRef, ++ { ++ for arg in args { ++ self.arg(arg); ++ } ++ self ++ } ++} + +-pub use args::*; +-pub use child::*; +-pub use command::*; +-pub use envs::*; ++impl Default for CommandArgs { ++ fn default() -> Self { ++ Self::new() ++ } ++} + +-use stdio::{Stdio, StdioLevel}; ++impl IntoIterator for CommandArgs { ++ type Item = OsString; + +-#[test] +-fn test() { +- use log::Level; +- use std::fs::File; ++ type IntoIter = std::vec::IntoIter; ++ ++ fn into_iter(self) -> Self::IntoIter { ++ self.0.into_iter() ++ } ++} ++ ++#[derive(Debug, Clone)] ++pub struct CommandEnvs(HashMap); ++ ++impl CommandEnvs { ++ pub fn new() -> Self { ++ Self(HashMap::new()) ++ } ++ ++ pub fn env(&mut self, key: K, value: V) -> &mut Self ++ where ++ K: AsRef, ++ V: AsRef, ++ { ++ self.0 ++ .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); ++ self ++ } ++ ++ pub fn envs(&mut self, vars: I) -> &mut Self ++ where ++ I: IntoIterator, ++ K: AsRef, ++ V: AsRef, ++ { ++ for (key, value) in vars { ++ self.env(key, value); ++ } ++ self ++ } ++} + +- use crate::ffi::OsStrExt; ++impl Default for CommandEnvs { ++ fn default() -> Self { ++ Self::new() ++ } ++} ++ ++impl IntoIterator for CommandEnvs { ++ type Item = (OsString, OsString); + +- println!("Testing Command::new()..."); +- let mut echo_cmd = Command::new("echo"); +- let mut env_cmd = Command::new("env"); +- let mut pwd_cmd = Command::new("pwd"); +- let mut grep_cmd = Command::new("grep"); +- let mut ls_cmd = Command::new("ls"); ++ type IntoIter = std::collections::hash_map::IntoIter; + +- let mut test_cmd = Command::new("test"); +- let mut cat_cmd = Command::new("cat"); +- +- let mut err_cmd = Command::new("/cmd/not/exist"); +- +- println!("Testing Command::arg()..."); +- echo_cmd.arg("Test:"); +- +- test_cmd.arg("1"); +- ls_cmd.arg("/file/not/exist"); +- +- println!("Testing Command::args()..."); +- echo_cmd.args(["Hello", "World!"]); +- +- println!("Testing Command::env_clear()..."); +- env_cmd.env_clear(); +- +- println!("Testing Command::env()..."); +- env_cmd.env("test_key1", "test_val1"); +- +- println!("Testing Command::envs()..."); +- env_cmd.envs([("test_key2", "test_val2"), ("test_key3", "test_val3")]); +- +- println!("Testing Command::current_dir()..."); +- pwd_cmd.current_dir("/tmp"); +- +- println!("Testing Command::stdin()..."); +- grep_cmd.stdin(File::open("/proc/self/maps").expect("Failed to open file")); +- grep_cmd.arg("vdso"); +- +- println!("Testing Command::stdout()..."); +- echo_cmd.stdout(Level::Info); +- +- println!("Testing Command::stderr()..."); +- echo_cmd.stderr(Level::Info); +- +- println!("Testing Command::spawn()..."); +- let mut echo_proc = echo_cmd.spawn().expect("Failed to spawn process"); +- let mut env_proc = env_cmd.spawn().expect("Failed to spawn process"); +- let mut pwd_proc = pwd_cmd.spawn().expect("Failed to spawn process"); +- let mut grep_proc = grep_cmd.spawn().expect("Failed to spawn process"); +- let mut ls_proc = ls_cmd.spawn().expect("Failed to spawn process"); +- +- let mut test_proc = test_cmd.spawn().expect("Failed to spawn process"); +- let mut cat_proc = cat_cmd.spawn().expect("Failed to spawn process"); +- +- assert_eq!(err_cmd.spawn().is_err(), true); +- +- println!("Testing Child::kill()..."); +- cat_proc.kill().expect("Failed to kill process"); +- +- println!("Testing Child::wait()..."); +- let test_status = test_proc.wait().expect("Failed to wait process"); +- let cat_status = cat_proc.wait().expect("Process should not be waited"); +- +- println!("Testing Child::wait_with_output()..."); +- let echo_output = echo_proc +- .wait_with_output() +- .expect("Failed to wait process"); +- let env_output = env_proc.wait_with_output().expect("Failed to wait process"); +- let pwd_output = pwd_proc.wait_with_output().expect("Failed to wait process"); +- let grep_output = grep_proc +- .wait_with_output() +- .expect("Failed to wait process"); +- let ls_output = ls_proc.wait_with_output().expect("Failed to wait process"); +- +- println!("Testing ExitStatus::exit_code()..."); +- assert_eq!(test_status.exit_code(), 0); +- assert_eq!(cat_status.exit_code(), 137); +- +- println!("Testing ExitStatus::exit_ok()..."); +- assert_eq!(test_status.exit_ok().is_ok(), true); +- assert_eq!(cat_status.exit_ok().is_ok(), false); +- +- println!("Testing ExitStatus::success()..."); +- assert_eq!(test_status.success(), true); +- assert_eq!(cat_status.success(), false); +- +- println!("Testing Output::exit_code()..."); +- assert_eq!(echo_output.exit_code(), 0); +- assert_eq!(env_output.exit_code(), 0); +- assert_eq!(pwd_output.exit_code(), 0); +- assert_eq!(grep_output.exit_code(), 0); +- assert_eq!(ls_output.exit_code(), 2); +- +- println!("Testing Output::exit_ok()..."); +- assert_eq!(echo_output.exit_ok().is_ok(), true); +- assert_eq!(env_output.exit_ok().is_ok(), true); +- assert_eq!(pwd_output.exit_ok().is_ok(), true); +- assert_eq!(grep_output.exit_ok().is_ok(), true); +- assert_eq!(ls_output.exit_ok().is_ok(), false); +- +- println!("Testing Output::success()..."); +- assert_eq!(echo_output.success(), true); +- assert_eq!(env_output.success(), true); +- assert_eq!(pwd_output.success(), true); +- assert_eq!(grep_output.success(), true); +- assert_eq!(ls_output.success(), false); +- +- println!("Testing Output::stdout..."); +- assert_eq!(echo_output.stdout.is_empty(), false); +- assert_eq!(env_output.stdout.is_empty(), false); +- assert_eq!(pwd_output.stdout.is_empty(), false); +- assert_eq!(grep_output.stdout.is_empty(), false); +- assert_eq!(ls_output.stdout.is_empty(), true); +- +- assert_eq!(echo_output.stdout, "Test: Hello World!"); +- assert_eq!( +- env_output.stdout, +- "test_key1=test_val1\ntest_key2=test_val2\ntest_key3=test_val3" +- ); +- assert_eq!(pwd_output.stdout, "/tmp"); +- assert_eq!(grep_output.stdout.contains("vdso"), true); +- +- println!("Testing ProcessOutput::stderr..."); +- assert_eq!(echo_output.stderr.is_empty(), true); +- assert_eq!(env_output.stderr.is_empty(), true); +- assert_eq!(pwd_output.stderr.is_empty(), true); +- assert_eq!(grep_output.stderr.is_empty(), true); +- assert_eq!(ls_output.stderr.is_empty(), false); ++ fn into_iter(self) -> Self::IntoIter { ++ self.0.into_iter() ++ } ++} ++ ++pub struct Command { ++ inner: process::Command, ++ log_level: output::LogLevel, ++} ++ ++impl Command { ++ pub fn new>(program: S) -> Self { ++ Self { ++ inner: process::Command::new(program), ++ log_level: output::LogLevel::default(), ++ } ++ } ++ ++ pub fn arg>(&mut self, arg: S) -> &mut Self { ++ self.inner.arg(arg); ++ self ++ } ++ ++ pub fn args(&mut self, args: I) -> &mut Self ++ where ++ I: IntoIterator, ++ S: AsRef, ++ { ++ for arg in args { ++ self.arg(arg.as_ref()); ++ } ++ self ++ } ++ ++ pub fn env(&mut self, key: K, val: V) -> &mut Self ++ where ++ K: AsRef, ++ V: AsRef, ++ { ++ self.inner.env(key, val); ++ self ++ } ++ ++ pub fn envs(&mut self, vars: I) -> &mut Self ++ where ++ I: IntoIterator, ++ K: AsRef, ++ V: AsRef, ++ { ++ for (key, val) in vars { ++ self.env(key, val); ++ } ++ self ++ } ++ ++ pub fn env_clear(&mut self) -> &mut Self { ++ self.inner.env_clear(); ++ self ++ } ++ ++ pub fn current_dir>(&mut self, dir: P) -> &mut Self { ++ self.inner.current_dir(dir); ++ self ++ } ++ ++ pub fn stdin>(&mut self, cfg: T) -> &mut Self { ++ self.inner.stdin(cfg); ++ self ++ } ++ ++ pub fn stdout(&mut self, level: Level) -> &mut Self { ++ self.log_level.stdout = Some(level); ++ self ++ } ++ ++ pub fn stderr(&mut self, level: Level) -> &mut Self { ++ self.log_level.stderr = Some(level); ++ self ++ } ++ ++ pub fn pipe_output(&mut self) -> &mut Self { ++ self.inner ++ .stdout(process::Stdio::piped()) ++ .stderr(process::Stdio::piped()); ++ self ++ } ++ ++ pub fn ignore_output(&mut self) -> &mut Self { ++ self.inner ++ .stdout(process::Stdio::null()) ++ .stderr(process::Stdio::null()); ++ self ++ } ++ ++ pub fn spawn(&mut self) -> Result { ++ let name = Path::new(self.inner.get_program()) ++ .file_name() ++ .map(|s| s.to_string_lossy().to_string()) ++ .unwrap_or_default(); ++ ++ trace!("Executing {:?}", self.inner); ++ let child = self ++ .inner ++ .spawn() ++ .with_context(|| format!("Failed to start {}", name))?; ++ let log_level = self.log_level; ++ ++ Ok(child::Child { ++ name, ++ child, ++ log_level, ++ }) ++ } ++ ++ pub fn run(&mut self) -> Result { ++ self.ignore_output().spawn()?.wait() ++ } ++ ++ pub fn run_with_output(&mut self) -> Result { ++ self.pipe_output().spawn()?.wait_with_output() ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use log::Level; ++ use std::ffi::OsStr; ++ use std::path::Path; ++ use std::time::Duration; ++ ++ #[test] ++ fn test_command_args_new() { ++ let args = CommandArgs::new(); ++ assert!(args.0.is_empty()); ++ } ++ ++ #[test] ++ fn test_command_args_default() { ++ let args = CommandArgs::default(); ++ assert!(args.0.is_empty()); ++ } ++ ++ #[test] ++ fn test_command_args_arg() { ++ let mut args = CommandArgs::new(); ++ args.arg("test"); ++ assert_eq!(args.0.len(), 1); ++ assert_eq!(args.0[0], OsStr::new("test")); ++ } ++ ++ #[test] ++ fn test_command_args_args() { ++ let mut args = CommandArgs::new(); ++ args.args(vec!["arg1", "arg2"]); ++ assert_eq!(args.0.len(), 2); ++ assert_eq!(args.0[0], OsStr::new("arg1")); ++ assert_eq!(args.0[1], OsStr::new("arg2")); ++ } ++ ++ #[test] ++ fn test_command_args_into_iter() { ++ let mut args = CommandArgs::new(); ++ args.args(vec!["a", "b", "c"]); ++ let mut iter = args.into_iter(); ++ assert_eq!(iter.next(), Some(OsString::from("a"))); ++ assert_eq!(iter.next(), Some(OsString::from("b"))); ++ assert_eq!(iter.next(), Some(OsString::from("c"))); ++ assert_eq!(iter.next(), None); ++ } ++ ++ #[test] ++ fn test_command_envs_new() { ++ let envs = CommandEnvs::new(); ++ assert!(envs.0.is_empty()); ++ } ++ ++ #[test] ++ fn test_command_envs_default() { ++ let envs = CommandEnvs::default(); ++ assert!(envs.0.is_empty()); ++ } ++ ++ #[test] ++ fn test_command_envs_env() { ++ let mut envs = CommandEnvs::new(); ++ envs.env("KEY", "VALUE"); ++ assert_eq!(envs.0.len(), 1); ++ assert_eq!( ++ envs.0.get(&OsString::from("KEY")), ++ Some(&OsString::from("VALUE")) ++ ); ++ } ++ ++ #[test] ++ fn test_command_envs_envs() { ++ let mut envs = CommandEnvs::new(); ++ envs.envs([("K1", "V1"), ("K2", "V2")]); ++ assert_eq!(envs.0.len(), 2); ++ assert_eq!( ++ envs.0.get(&OsString::from("K1")), ++ Some(&OsString::from("V1")) ++ ); ++ assert_eq!( ++ envs.0.get(&OsString::from("K2")), ++ Some(&OsString::from("V2")) ++ ); ++ } ++ ++ #[test] ++ fn test_command_new() { ++ let cmd = Command::new("test"); ++ assert_eq!(cmd.inner.get_program(), OsStr::new("test")); ++ assert!(cmd.inner.get_args().next().is_none()); ++ } ++ ++ #[test] ++ fn test_command_arg() { ++ let mut cmd = Command::new("test"); ++ cmd.arg("arg1"); ++ let mut args = cmd.inner.get_args(); ++ assert_eq!(args.next(), Some(OsStr::new("arg1"))); ++ assert!(args.next().is_none()); ++ } ++ ++ #[test] ++ fn test_command_args() { ++ let mut cmd = Command::new("test"); ++ cmd.args(["a", "b"]); ++ let mut args = cmd.inner.get_args(); ++ assert_eq!(args.next(), Some(OsStr::new("a"))); ++ assert_eq!(args.next(), Some(OsStr::new("b"))); ++ assert!(args.next().is_none()); ++ } ++ ++ #[test] ++ fn test_command_env() { ++ let mut cmd = Command::new("test"); ++ cmd.env("K", "V"); ++ let envs = cmd.inner.get_envs().collect::>(); ++ assert_eq!(envs.get(&OsStr::new("K")), Some(&Some(OsStr::new("V")))); ++ } ++ ++ #[test] ++ fn test_command_envs() { ++ let mut cmd = Command::new("test"); ++ cmd.envs([("K1", "V1"), ("K2", "V2")]); ++ ++ let envs = cmd.inner.get_envs().collect::>(); ++ assert_eq!(envs.len(), 2); ++ assert_eq!(envs.get(&OsStr::new("K1")), Some(&Some(OsStr::new("V1")))); ++ assert_eq!(envs.get(&OsStr::new("K2")), Some(&Some(OsStr::new("V2")))); ++ } ++ ++ #[test] ++ fn test_command_env_clear() { ++ let mut cmd = Command::new("test"); ++ cmd.env("K", "V").env_clear(); ++ let envs = cmd.inner.get_envs().collect::>(); ++ assert!(envs.is_empty()); ++ } ++ ++ #[test] ++ fn test_command_current_dir() { ++ let mut cmd = Command::new("test"); ++ cmd.current_dir("/tmp"); ++ assert_eq!(cmd.inner.get_current_dir(), Some(Path::new("/tmp"))); ++ } ++ ++ #[test] ++ fn test_command_stdout_stderr() { ++ let mut cmd = Command::new("test"); ++ cmd.stdout(Level::Info).stderr(Level::Error); ++ assert_eq!(cmd.log_level.stdout, Some(Level::Info)); ++ assert_eq!(cmd.log_level.stderr, Some(Level::Error)); ++ } ++ ++ #[test] ++ fn test_command_pipe_output() { ++ let mut cmd = Command::new("test"); ++ ++ cmd.pipe_output(); ++ assert!(cmd.inner.spawn().unwrap().stdout.is_some()); ++ assert!(cmd.inner.spawn().unwrap().stderr.is_some()); ++ } ++ ++ #[test] ++ fn test_command_ignore_output() { ++ let mut cmd = Command::new("test"); ++ ++ cmd.pipe_output(); ++ assert!(cmd.inner.spawn().unwrap().stdout.is_some()); ++ assert!(cmd.inner.spawn().unwrap().stderr.is_some()); ++ } ++ ++ #[test] ++ fn test_command_spawn_kill() { ++ let mut cmd = Command::new("yes"); ++ cmd.ignore_output(); ++ ++ let mut child = cmd.spawn().unwrap(); ++ std::thread::sleep(Duration::from_millis(100)); ++ ++ assert!(child.kill().is_ok()); ++ } ++ ++ #[test] ++ fn test_command_spawn_wait() { ++ let mut cmd = Command::new("echo"); ++ cmd.arg("test").ignore_output(); ++ ++ let mut child = cmd.spawn().unwrap(); ++ let status = child.wait().unwrap(); ++ assert!(status.success()); ++ } ++ ++ #[test] ++ fn test_command_run() { ++ let mut cmd = Command::new("true"); ++ let status = cmd.run().unwrap(); ++ assert!(status.success()); ++ } ++ ++ #[test] ++ fn test_command_run_with_output() { ++ let mut cmd = Command::new("env"); ++ let output = cmd.run_with_output().unwrap(); ++ ++ assert!(output.status.success()); ++ assert!(!output.stdout.is_empty()); ++ } + } +diff --git a/syscare-common/src/process/output.rs b/syscare-common/src/process/output.rs +new file mode 100644 +index 0000000..5e97c7e +--- /dev/null ++++ b/syscare-common/src/process/output.rs +@@ -0,0 +1,179 @@ ++// SPDX-License-Identifier: Mulan PSL v2 ++/* ++ * Copyright (c) 2024 Huawei Technologies Co., Ltd. ++ * syscare-common is licensed under Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, ++ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, ++ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use std::{ ++ ffi::OsString, ++ io::Read, ++ os::unix::{ffi::OsStringExt, io::AsRawFd}, ++ process::{ChildStderr, ChildStdout}, ++}; ++ ++use log::{error, log, Level}; ++use nix::poll::{poll, PollFd, PollFlags}; ++ ++const STREAM_BUFFER_SIZE: usize = 4096; ++ ++#[derive(Debug, Clone, Copy)] ++pub struct LogLevel { ++ pub stdout: Option, ++ pub stderr: Option, ++} ++ ++impl Default for LogLevel { ++ fn default() -> Self { ++ Self { ++ stdout: None, ++ stderr: Some(Level::Error), ++ } ++ } ++} ++ ++struct Stream { ++ stream: R, ++ buffer: Vec, ++ offset: usize, ++ log_level: Option, ++ is_closed: bool, ++} ++ ++impl Stream { ++ fn new(stream: R, log_level: Option) -> Self { ++ Self { ++ stream, ++ buffer: Vec::with_capacity(STREAM_BUFFER_SIZE), ++ offset: 0, ++ log_level, ++ is_closed: false, ++ } ++ } ++ ++ fn read_buf(&mut self) -> std::io::Result { ++ if self.buffer.capacity().wrapping_sub(self.buffer.len()) < STREAM_BUFFER_SIZE { ++ self.buffer.reserve(STREAM_BUFFER_SIZE); ++ } ++ ++ let spare_cap = self.buffer.spare_capacity_mut(); ++ let spare_buf = unsafe { ++ std::slice::from_raw_parts_mut(spare_cap.as_mut_ptr() as *mut u8, spare_cap.len()) ++ }; ++ ++ let len = self.stream.read(spare_buf)?; ++ unsafe { ++ self.buffer.set_len(self.buffer.len() + len); ++ } ++ ++ Ok(len) ++ } ++ ++ fn print_logs(&mut self) { ++ if let Some(level) = self.log_level { ++ let start = self.offset; ++ if start >= self.buffer.len() { ++ return; ++ } ++ ++ let slice = if !self.is_closed { ++ let end = match self.buffer[start..].iter().rposition(|&b| b == b'\n') { ++ Some(pos) => start + pos, ++ None => return, ++ }; ++ self.offset = end + 1; // skip '\n' ++ &self.buffer[start..end] ++ } else { ++ self.offset = self.buffer.len(); ++ &self.buffer[start..] ++ }; ++ if slice.is_empty() { ++ return; ++ } ++ ++ let lines = slice.split(|&b| b == b'\n').map(String::from_utf8_lossy); ++ for line in lines { ++ log!(level, "{}", line); ++ } ++ } ++ } ++ ++ fn handle_revents(&mut self, revents: PollFlags) { ++ if revents.contains(PollFlags::POLLIN) { ++ match self.read_buf() { ++ Ok(0) => self.is_closed = true, // EOF ++ Ok(_) => {} ++ Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {} ++ Err(e) => { ++ error!("Failed to read stream, {}", e); ++ self.is_closed = true; ++ } ++ } ++ } ++ if revents.contains(PollFlags::POLLHUP) { ++ self.is_closed = true; ++ } ++ ++ self.print_logs(); ++ } ++} ++ ++pub struct Outputs { ++ fds: [PollFd; 2], ++ stdout: Stream, ++ stderr: Stream, ++} ++ ++impl Outputs { ++ pub fn new(stdout: ChildStdout, stderr: ChildStderr, log_level: LogLevel) -> Self { ++ Self { ++ fds: [ ++ PollFd::new(stdout.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), ++ PollFd::new(stderr.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), ++ ], ++ stdout: Stream::new(stdout, log_level.stdout), ++ stderr: Stream::new(stderr, log_level.stderr), ++ } ++ } ++ ++ pub fn redirect(mut self) -> (OsString, OsString) { ++ const POLL_TIMEOUT: i32 = -1; ++ ++ loop { ++ match poll(&mut self.fds, POLL_TIMEOUT) { ++ Ok(events) => { ++ if events == 0 { ++ break; ++ } ++ for (i, fd) in self.fds.iter().enumerate() { ++ let revents = fd.revents().expect("Invalid poll event"); ++ match i { ++ 0 => self.stdout.handle_revents(revents), ++ 1 => self.stderr.handle_revents(revents), ++ _ => unreachable!("Invalid poll fd"), ++ }; ++ } ++ } ++ Err(e) => { ++ error!("Failed to poll events, {}", e); ++ break; ++ } ++ } ++ if self.stdout.is_closed && self.stderr.is_closed { ++ break; ++ } ++ } ++ ++ ( ++ OsString::from_vec(self.stdout.buffer), ++ OsString::from_vec(self.stderr.buffer), ++ ) ++ } ++} +diff --git a/syscare-common/src/process/stdio.rs b/syscare-common/src/process/stdio.rs +deleted file mode 100644 +index 9a93e56..0000000 +--- a/syscare-common/src/process/stdio.rs ++++ /dev/null +@@ -1,179 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscare-common is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ +- collections::HashMap, +- ffi::OsString, +- io::BufReader, +- os::unix::{ +- ffi::OsStringExt, +- io::{AsRawFd, RawFd}, +- }, +- process::{ChildStderr, ChildStdout}, +- thread::JoinHandle, +-}; +- +-use anyhow::{Context, Result}; +-use log::{error, log, Level}; +- +-use crate::io::{BufReadOsLines, OsLines, Select, SelectResult}; +- +-#[derive(Debug, Clone, Copy)] +-pub struct StdioLevel { +- pub(super) stdout: Option, +- pub(super) stderr: Option, +-} +- +-impl Default for StdioLevel { +- fn default() -> Self { +- Self { +- stdout: None, +- stderr: Some(Level::Error), +- } +- } +-} +- +-pub enum StdioOutput { +- Stdout(OsString), +- Stderr(OsString), +-} +- +-enum StdioLines { +- Stdout(OsLines>), +- Stderr(OsLines>), +-} +- +-struct StdioReader { +- select: Select, +- stdio_map: HashMap, +- line_buf: Vec, +-} +- +-impl StdioReader { +- fn new(stdout: ChildStdout, stderr: ChildStderr) -> Self { +- let line_buf = Vec::new(); +- let stdio_map = HashMap::from([ +- ( +- stdout.as_raw_fd(), +- StdioLines::Stdout(BufReader::new(stdout).os_lines()), +- ), +- ( +- stderr.as_raw_fd(), +- StdioLines::Stderr(BufReader::new(stderr).os_lines()), +- ), +- ]); +- let select = Select::new(stdio_map.keys().copied()); +- +- Self { +- select, +- stdio_map, +- line_buf, +- } +- } +-} +- +-impl Iterator for StdioReader { +- type Item = StdioOutput; +- +- fn next(&mut self) -> Option { +- match self.select.select().context("Failed to select stdio") { +- Ok(result) => { +- let stdio_map = &mut self.stdio_map; +- let outputs = result.into_iter().filter_map(|income| match income { +- SelectResult::Readable(fd) => { +- stdio_map.get_mut(&fd).and_then(|stdio| match stdio { +- StdioLines::Stdout(lines) => { +- lines.next().and_then(Result::ok).map(StdioOutput::Stdout) +- } +- StdioLines::Stderr(lines) => { +- lines.next().and_then(Result::ok).map(StdioOutput::Stderr) +- } +- }) +- } +- _ => None, +- }); +- self.line_buf.extend(outputs); +- } +- Err(e) => { +- error!("{:?}", e); +- } +- }; +- +- self.line_buf.pop() +- } +-} +- +-pub struct Stdio { +- name: String, +- stdout: ChildStdout, +- stderr: ChildStderr, +- level: StdioLevel, +-} +- +-impl Stdio { +- pub fn new(name: String, stdout: ChildStdout, stderr: ChildStderr, level: StdioLevel) -> Self { +- Self { +- name, +- stdout, +- stderr, +- level, +- } +- } +- +- pub fn capture(self) -> Result> { +- let stdio_level = self.level; +- let stdio_reader = StdioReader::new(self.stdout, self.stderr); +- +- let thread_name = self.name.as_str(); +- let thread = std::thread::Builder::new() +- .name(thread_name.to_string()) +- .spawn(move || -> (OsString, OsString) { +- let mut stdout_buf = Vec::new(); +- let mut stderr_buf = Vec::new(); +- +- for output in stdio_reader { +- match output { +- StdioOutput::Stdout(str) => { +- if let Some(level) = stdio_level.stdout { +- log!(level, "{}", str.to_string_lossy()); +- } +- stdout_buf.extend(str.into_vec()); +- stdout_buf.push(b'\n'); +- } +- StdioOutput::Stderr(str) => { +- if let Some(level) = stdio_level.stderr { +- log!(level, "{}", str.to_string_lossy()); +- } +- stderr_buf.extend(str.into_vec()); +- stderr_buf.push(b'\n'); +- } +- } +- } +- if stdout_buf.ends_with(b"\n") { +- stdout_buf.pop(); +- } +- if stderr_buf.ends_with(b"\n") { +- stderr_buf.pop(); +- } +- +- ( +- OsString::from_vec(stdout_buf), +- OsString::from_vec(stderr_buf), +- ) +- }) +- .with_context(|| format!("Failed to create thread {}", thread_name))?; +- +- Ok(thread) +- } +-} +diff --git a/syscare-common/src/util/digest.rs b/syscare-common/src/util/digest.rs +index 086b636..4ed7357 100644 +--- a/syscare-common/src/util/digest.rs ++++ b/syscare-common/src/util/digest.rs +@@ -14,23 +14,21 @@ + + use std::path::Path; + +-use sha2::Digest; +-use sha2::Sha256; ++use nix::errno::Errno; ++use sha2::{Digest, Sha256}; + + use crate::fs; + +-pub fn bytes>(bytes: S) -> String { +- let mut hasher = Sha256::new(); +- hasher.update(bytes); +- +- format!("{:#x}", hasher.finalize()) ++pub fn bytes>(bytes: T) -> String { ++ format!("{:#x}", Sha256::digest(bytes)) + } + +-pub fn file>(file: P) -> std::io::Result { +- let mut hasher = Sha256::new(); +- hasher.update(fs::read(file)?); +- +- Ok(format!("{:#x}", hasher.finalize())) ++pub fn file>(path: P) -> std::io::Result { ++ let file_path = path.as_ref(); ++ if !file_path.is_file() { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ Ok(self::bytes(&*fs::mmap(file_path)?)) + } + + pub fn file_list(file_list: I) -> std::io::Result +@@ -39,9 +37,201 @@ where + P: AsRef, + { + let mut hasher = Sha256::new(); ++ + for file in file_list { +- hasher.update(fs::read(file)?); ++ let file_path = file.as_ref(); ++ if file_path.is_file() { ++ hasher.update(&*fs::mmap(file)?); ++ } + } + + Ok(format!("{:#x}", hasher.finalize())) + } ++ ++pub fn dir>(path: P) -> std::io::Result { ++ let dir_path = path.as_ref(); ++ if !dir_path.is_dir() { ++ return Err(std::io::Error::from(Errno::EINVAL)); ++ } ++ ++ let mut file_list = fs::list_files(path, fs::TraverseOptions { recursive: true })?; ++ file_list.sort_unstable(); ++ ++ self::file_list(file_list) ++} ++ ++pub fn path>(path: P) -> std::io::Result { ++ if path.as_ref().is_file() { ++ self::file(path) ++ } else { ++ self::dir(path) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use std::{ ++ fs::File, ++ io::Write, ++ path::PathBuf, ++ time::{SystemTime, UNIX_EPOCH}, ++ }; ++ ++ use super::*; ++ ++ fn unique_name(prefix: &str) -> std::io::Result { ++ let timestamp = SystemTime::now() ++ .duration_since(UNIX_EPOCH) ++ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; ++ Ok(format!("{}_{}", prefix, timestamp.as_nanos())) ++ } ++ ++ fn create_temp_file(content: &[u8]) -> std::io::Result { ++ let temp_file = std::env::temp_dir().join(self::unique_name("digest_test")?); ++ ++ let mut file = File::create(&temp_file)?; ++ file.write_all(content)?; ++ ++ Ok(temp_file) ++ } ++ ++ fn create_temp_dir() -> std::io::Result { ++ let temp_dir = std::env::temp_dir(); ++ let test_dir = temp_dir.join(unique_name("test_dir")?); ++ ++ fs::create_dir(&test_dir)?; ++ File::create(test_dir.join("file1.txt"))?.write_all(b"file1")?; ++ File::create(test_dir.join("file2.bin"))?.write_all(b"file2")?; ++ fs::create_dir(test_dir.join("subdir"))?; ++ File::create(test_dir.join("subdir/file3.txt"))?.write_all(b"file3")?; ++ ++ Ok(test_dir) ++ } ++ ++ #[test] ++ fn test_bytes() -> std::io::Result<()> { ++ assert_eq!( ++ self::bytes(b"hello"), ++ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" ++ ); ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_file() -> std::io::Result<()> { ++ let file_path = self::create_temp_file(b"hello")?; ++ let hash = self::file(&file_path)?; ++ ++ std::fs::remove_file(&file_path).ok(); ++ assert_eq!( ++ hash, ++ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" ++ ); ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_file_not_exists() -> std::io::Result<()> { ++ let result = self::file("/non_exist_file"); ++ ++ assert!(result.is_err()); ++ if let Err(e) = &result { ++ assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); ++ } ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_file_is_dir() -> std::io::Result<()> { ++ let result = self::file(std::env::temp_dir()); ++ ++ assert!(result.is_err()); ++ if let Err(e) = &result { ++ assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); ++ } ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_file_list() -> std::io::Result<()> { ++ let files = vec![ ++ self::create_temp_file(b"file1")?, ++ self::create_temp_file(b"file2")?, ++ self::create_temp_file(b"file3")?, ++ ]; ++ ++ let hash = self::file_list(&files)?; ++ for file in files { ++ std::fs::remove_file(file)?; ++ } ++ assert_eq!( ++ hash, ++ "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" ++ ); ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_dir() -> std::io::Result<()> { ++ let test_dir = self::create_temp_dir()?; ++ let hash = self::dir(&test_dir)?; ++ std::fs::remove_dir_all(test_dir)?; ++ ++ assert_eq!( ++ hash, ++ "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" ++ ); ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_dir_not_exists() -> std::io::Result<()> { ++ let result = self::dir("/non_exist_file"); ++ ++ assert!(result.is_err()); ++ if let Err(e) = &result { ++ assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); ++ } ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_dir_is_file() -> std::io::Result<()> { ++ let file_path = self::create_temp_file(b"hello")?; ++ let result = self::dir(&file_path); ++ std::fs::remove_file(file_path)?; ++ ++ assert!(result.is_err()); ++ if let Err(e) = &result { ++ assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); ++ } ++ ++ Ok(()) ++ } ++ ++ #[test] ++ fn test_path() -> std::io::Result<()> { ++ let file_path = self::create_temp_file(b"hello")?; ++ let dir_path = self::create_temp_dir()?; ++ ++ let file_hash = self::path(&file_path)?; ++ let dir_hash = self::path(&dir_path)?; ++ std::fs::remove_file(&file_path)?; ++ std::fs::remove_dir_all(&dir_path)?; ++ ++ assert_eq!( ++ file_hash, ++ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" ++ ); ++ assert_eq!( ++ dir_hash, ++ "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" ++ ); ++ Ok(()) ++ } ++} +-- +2.43.0 + diff --git a/0135-syscared-adapt-common-crate-change.patch b/0135-syscared-adapt-common-crate-change.patch new file mode 100644 index 0000000000000000000000000000000000000000..713e193e4aa95e6d8ce5593f93a47106bc0d383b --- /dev/null +++ b/0135-syscared-adapt-common-crate-change.patch @@ -0,0 +1,98 @@ +From 217a30547f8c6c52cbed382a7e2162b3caf90899 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 29 May 2025 18:13:14 +0800 +Subject: [PATCH] syscared: adapt common crate change + +Signed-off-by: renoseven +--- + syscared/src/main.rs | 21 ++++++++------------- + syscared/src/patch/driver/kpatch/sys.rs | 12 ++++++------ + 2 files changed, 14 insertions(+), 19 deletions(-) + +diff --git a/syscared/src/main.rs b/syscared/src/main.rs +index f965497..bfff3d7 100644 +--- a/syscared/src/main.rs ++++ b/syscared/src/main.rs +@@ -59,10 +59,6 @@ const LOG_DIR_PERM: u32 = 0o700; + const SOCKET_FILE_PERM: u32 = 0o660; + const SOCKET_FILE_PERM_STRICT: u32 = 0o600; + +-const MAIN_THREAD_NAME: &str = "main"; +-const UNNAMED_THREAD_NAME: &str = ""; +-const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; +- + struct Daemon { + args: Arguments, + config: Config, +@@ -74,15 +70,14 @@ impl Daemon { + now: &mut DeferredNow, + record: &Record, + ) -> std::io::Result<()> { ++ const UNNAMED_THREAD_NAME: &str = ""; ++ const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; ++ + thread_local! { +- static THREAD_NAME: String = std::thread::current().name().and_then(|name| { +- if name == MAIN_THREAD_NAME { +- return os::process::name().to_str(); +- } +- Some(name) +- }) +- .unwrap_or(UNNAMED_THREAD_NAME) +- .to_string(); ++ static THREAD_NAME: String = std::thread::current() ++ .name() ++ .unwrap_or(UNNAMED_THREAD_NAME) ++ .to_string(); + } + + THREAD_NAME.with(|thread_name| { +@@ -100,7 +95,7 @@ impl Daemon { + fn new() -> Result { + // Check root permission + ensure!( +- os::user::id() == 0, ++ os::user::uid() == 0, + "This command has to be run with superuser privileges (under the root user on most systems)." + ); + +diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs +index fd5160c..adabfb4 100644 +--- a/syscared/src/patch/driver/kpatch/sys.rs ++++ b/syscared/src/patch/driver/kpatch/sys.rs +@@ -19,7 +19,7 @@ use log::debug; + use nix::kmod; + + use syscare_abi::PatchStatus; +-use syscare_common::{ffi::OsStrExt, fs, os}; ++use syscare_common::{ffi::OsStrExt, fs, os::selinux}; + + use crate::patch::entity::KernelPatch; + +@@ -39,7 +39,7 @@ pub fn list_kernel_modules() -> Result> { + pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { + const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; + +- if os::selinux::get_status()? != os::selinux::Status::Enforcing { ++ if selinux::get_status() != selinux::Status::Enforcing { + return Ok(()); + } + +@@ -47,10 +47,10 @@ pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { + "Relabeling patch module '{}'...", + patch.module_name.to_string_lossy() + ); +- let mut sec_context = os::selinux::get_security_context(&patch.patch_file)?; +- if sec_context.kind != KPATCH_PATCH_SEC_TYPE { +- sec_context.kind = OsString::from(KPATCH_PATCH_SEC_TYPE); +- os::selinux::set_security_context(&patch.patch_file, sec_context)?; ++ let mut sec_context = selinux::get_security_context(&patch.patch_file)?; ++ if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { ++ sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; ++ selinux::set_security_context(&patch.patch_file, &sec_context)?; + } + + Ok(()) +-- +2.43.0 + diff --git a/0136-syscared-rewrite-patch-parsing-management.patch b/0136-syscared-rewrite-patch-parsing-management.patch new file mode 100644 index 0000000000000000000000000000000000000000..a58ea1ca9d92c797291eb25f6a48e1e63da0ff13 --- /dev/null +++ b/0136-syscared-rewrite-patch-parsing-management.patch @@ -0,0 +1,3184 @@ +From b491611bc9ddf46af6b428e3d77e4e93731e2711 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Tue, 3 Jun 2025 15:55:03 +0800 +Subject: [PATCH] syscared: rewrite patch parsing & management + +Signed-off-by: renoseven +--- + syscared/src/config.rs | 22 +- + syscared/src/patch/driver/kpatch/mod.rs | 302 +++++++---------- + syscared/src/patch/driver/kpatch/sys.rs | 119 +++---- + syscared/src/patch/driver/kpatch/target.rs | 144 +++----- + syscared/src/patch/driver/mod.rs | 84 +++-- + syscared/src/patch/driver/upatch/entity.rs | 60 ---- + syscared/src/patch/driver/upatch/mod.rs | 348 +++++++------------- + syscared/src/patch/driver/upatch/monitor.rs | 30 +- + syscared/src/patch/driver/upatch/sys.rs | 32 +- + syscared/src/patch/driver/upatch/target.rs | 158 ++++----- + syscared/src/patch/entity/kpatch.rs | 224 ++++++++++++- + syscared/src/patch/entity/patch.rs | 5 +- + syscared/src/patch/entity/symbol.rs | 54 --- + syscared/src/patch/entity/upatch.rs | 181 +++++++++- + syscared/src/patch/manager.rs | 169 +++++++--- + syscared/src/patch/mod.rs | 1 - + syscared/src/patch/resolver/kpatch.rs | 226 ------------- + syscared/src/patch/resolver/mod.rs | 63 ---- + syscared/src/patch/resolver/upatch.rs | 182 ---------- + 19 files changed, 1053 insertions(+), 1351 deletions(-) + delete mode 100644 syscared/src/patch/driver/upatch/entity.rs + delete mode 100644 syscared/src/patch/entity/symbol.rs + delete mode 100644 syscared/src/patch/resolver/kpatch.rs + delete mode 100644 syscared/src/patch/resolver/mod.rs + delete mode 100644 syscared/src/patch/resolver/upatch.rs + +diff --git a/syscared/src/config.rs b/syscared/src/config.rs +index 7103273..5e0d736 100644 +--- a/syscared/src/config.rs ++++ b/syscared/src/config.rs +@@ -83,17 +83,27 @@ pub struct Config { + impl Config { + pub fn parse>(path: P) -> Result { + let config_path = path.as_ref(); +- let instance = serde_yaml::from_reader(fs::open_file(config_path)?) +- .map_err(|_| anyhow!("Failed to parse config {}", config_path.display()))?; +- +- Ok(instance) ++ let config = serde_yaml::from_reader(fs::open_file(config_path)?).map_err(|e| { ++ anyhow!( ++ "Failed to parse config '{}', {}", ++ config_path.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ Ok(config) + } + + pub fn write>(&self, path: P) -> Result<()> { + let config_path = path.as_ref(); + let config_file = fs::create_file(config_path)?; +- serde_yaml::to_writer(config_file, self) +- .map_err(|_| anyhow!("Failed to write config {}", config_path.display()))?; ++ serde_yaml::to_writer(config_file, self).map_err(|e| { ++ anyhow!( ++ "Failed to write config '{}', {}", ++ config_path.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; + + Ok(()) + } +diff --git a/syscared/src/patch/driver/kpatch/mod.rs b/syscared/src/patch/driver/kpatch/mod.rs +index ba177ac..8e39670 100644 +--- a/syscared/src/patch/driver/kpatch/mod.rs ++++ b/syscared/src/patch/driver/kpatch/mod.rs +@@ -13,103 +13,58 @@ + */ + + use std::{ +- ffi::{OsStr, OsString}, ++ collections::{HashMap, HashSet}, ++ ffi::OsString, + fmt::Write, + iter::FromIterator, + }; + +-use anyhow::{ensure, Result}; +-use indexmap::{indexset, IndexMap, IndexSet}; +-use log::{debug, info}; ++use anyhow::{anyhow, ensure, Context, Result}; ++use log::debug; + + use syscare_abi::PatchStatus; +-use syscare_common::{concat_os, os, util::digest}; +- +-use crate::{ +- config::KernelPatchConfig, +- patch::entity::{KernelPatch, KernelPatchFunction}, ++use syscare_common::{ ++ concat_os, ++ os::{self, kernel, selinux}, ++ util::digest, + }; + ++use crate::{config::KernelPatchConfig, patch::entity::KernelPatch}; ++ + mod sys; + mod target; + + use target::PatchTarget; + + pub struct KernelPatchDriver { +- target_map: IndexMap, +- blocked_targets: IndexSet, ++ target_map: HashMap, // object name -> object ++ blocked_targets: HashSet, + } + + impl KernelPatchDriver { + pub fn new(config: &KernelPatchConfig) -> Result { + Ok(Self { +- target_map: IndexMap::new(), +- blocked_targets: IndexSet::from_iter(config.blocked.iter().cloned()), ++ target_map: HashMap::new(), ++ blocked_targets: HashSet::from_iter(config.blocked.iter().cloned()), + }) + } + } + + impl KernelPatchDriver { +- fn group_patch_targets(patch: &KernelPatch) -> IndexSet<&OsStr> { +- let mut patch_targets = IndexSet::new(); +- +- for function in &patch.functions { +- patch_targets.insert(function.object.as_os_str()); +- } +- patch_targets +- } +- +- pub fn group_patch_functions( +- patch: &KernelPatch, +- ) -> IndexMap<&OsStr, Vec<&KernelPatchFunction>> { +- let mut patch_function_map: IndexMap<&OsStr, Vec<&KernelPatchFunction>> = IndexMap::new(); +- +- for function in &patch.functions { +- patch_function_map +- .entry(function.object.as_os_str()) +- .or_default() +- .push(function); +- } +- patch_function_map +- } +-} +- +-impl KernelPatchDriver { +- fn add_patch_target(&mut self, patch: &KernelPatch) { +- for target_name in Self::group_patch_targets(patch) { +- if !self.target_map.contains_key(target_name) { +- self.target_map.insert( +- target_name.to_os_string(), +- PatchTarget::new(target_name.to_os_string()), +- ); +- } ++ fn register_patch(&mut self, patch: &KernelPatch) { ++ for object_name in patch.functions.keys() { ++ self.target_map ++ .entry(object_name.clone()) ++ .or_insert_with(|| PatchTarget::new(object_name.clone())) ++ .add_patch(patch); + } + } + +- fn remove_patch_target(&mut self, patch: &KernelPatch) { +- for target_name in Self::group_patch_targets(patch) { +- if let Some(target) = self.target_map.get_mut(target_name) { +- if !target.has_function() { +- self.target_map.remove(target_name); +- } +- } +- } +- } +- +- fn add_patch_functions(&mut self, patch: &KernelPatch) { +- for (target_name, functions) in Self::group_patch_functions(patch) { +- if let Some(target) = self.target_map.get_mut(target_name) { +- target.add_functions(patch.uuid, functions); +- } +- } +- } +- +- fn remove_patch_functions(&mut self, patch: &KernelPatch) { +- for (target_name, functions) in Self::group_patch_functions(patch) { +- if let Some(target) = self.target_map.get_mut(target_name) { +- target.remove_functions(&patch.uuid, functions); +- } +- } ++ fn unregister_patch(&mut self, patch: &KernelPatch) { ++ self.target_map.retain(|_, object| { ++ object.remove_patch(patch); ++ object.is_patched() ++ }); + } + } + +@@ -130,13 +85,14 @@ impl KernelPatchDriver { + const KERNEL_NAME_PREFIX: &str = "kernel-"; + + let patch_target = patch.pkg_name.as_str(); ++ if !patch_target.starts_with(KERNEL_NAME_PREFIX) { ++ return Ok(()); ++ } ++ + let current_kernel = concat_os!(KERNEL_NAME_PREFIX, os::kernel::version()); + debug!("Patch target: '{}'", patch_target); + debug!("Current kernel: '{}'", current_kernel.to_string_lossy()); + +- if !patch_target.starts_with(KERNEL_NAME_PREFIX) { +- return Ok(()); +- } + ensure!( + current_kernel == patch_target, + "Kpatch: Patch is incompatible", +@@ -147,156 +103,134 @@ impl KernelPatchDriver { + fn check_dependency(patch: &KernelPatch) -> Result<()> { + const VMLINUX_MODULE_NAME: &str = "vmlinux"; + +- let mut non_exist_kmod = IndexSet::new(); +- +- let kmod_list = sys::list_kernel_modules()?; +- for kmod_name in Self::group_patch_targets(patch) { +- if kmod_name == VMLINUX_MODULE_NAME { +- continue; +- } +- if kmod_list.iter().any(|name| name == kmod_name) { +- continue; +- } +- non_exist_kmod.insert(kmod_name); +- } +- +- ensure!(non_exist_kmod.is_empty(), { +- let mut err_msg = String::new(); +- +- writeln!(&mut err_msg, "Kpatch: Patch target does not exist")?; +- for kmod_name in non_exist_kmod { +- writeln!(&mut err_msg, "* Module '{}'", kmod_name.to_string_lossy())?; ++ let depend_modules = patch.functions.keys().cloned().collect::>(); ++ let inserted_modules = ++ kernel::list_modules().context("Kpatch: Failed to list kernel modules")?; ++ let needed_modules = depend_modules ++ .difference(&inserted_modules) ++ .filter(|&module_name| module_name != VMLINUX_MODULE_NAME) ++ .collect::>(); ++ ++ ensure!(needed_modules.is_empty(), { ++ let mut msg = String::new(); ++ writeln!(msg, "Kpatch: Patch target does not exist")?; ++ for name in needed_modules { ++ writeln!(msg, "* Module '{}'", name.to_string_lossy())?; + } +- err_msg.pop(); +- +- err_msg ++ msg.pop(); ++ msg + }); + Ok(()) + } + +- pub fn check_conflict_functions(&self, patch: &KernelPatch) -> Result<()> { +- let mut conflict_patches = indexset! {}; +- +- let target_functions = Self::group_patch_functions(patch); +- for (target_name, functions) in target_functions { +- if let Some(target) = self.target_map.get(target_name) { +- conflict_patches.extend( +- target +- .get_conflicts(functions) +- .into_iter() +- .map(|record| record.uuid), +- ); +- } +- } +- +- ensure!(conflict_patches.is_empty(), { +- let mut err_msg = String::new(); ++ pub fn check_conflicted_patches(&self, patch: &KernelPatch) -> Result<()> { ++ let conflicted: HashSet<_> = self ++ .target_map ++ .values() ++ .flat_map(|object| object.get_conflicted_patches(patch)) ++ .collect(); + +- writeln!(&mut err_msg, "Kpatch: Patch is conflicted with")?; +- for uuid in conflict_patches.into_iter() { +- writeln!(&mut err_msg, "* Patch '{}'", uuid)?; ++ ensure!(conflicted.is_empty(), { ++ let mut msg = String::new(); ++ writeln!(msg, "Kpatch: Patch is conflicted with")?; ++ for uuid in conflicted { ++ writeln!(msg, "* Patch '{}'", uuid)?; + } +- err_msg.pop(); +- +- err_msg ++ msg.pop(); ++ msg + }); + Ok(()) + } + +- pub fn check_override_functions(&self, patch: &KernelPatch) -> Result<()> { +- let mut override_patches = indexset! {}; +- +- let target_functions = Self::group_patch_functions(patch); +- for (target_name, functions) in target_functions { +- if let Some(target) = self.target_map.get(target_name) { +- override_patches.extend( +- target +- .get_overrides(&patch.uuid, functions) +- .into_iter() +- .map(|record| record.uuid), +- ); +- } +- } +- +- ensure!(override_patches.is_empty(), { +- let mut err_msg = String::new(); ++ pub fn check_overridden_patches(&self, patch: &KernelPatch) -> Result<()> { ++ let overridden: HashSet<_> = self ++ .target_map ++ .values() ++ .flat_map(|object| object.get_overridden_patches(patch)) ++ .collect(); + +- writeln!(&mut err_msg, "Kpatch: Patch is overrided by")?; +- for uuid in override_patches.into_iter() { +- writeln!(&mut err_msg, "* Patch '{}'", uuid)?; ++ ensure!(overridden.is_empty(), { ++ let mut msg = String::new(); ++ writeln!(msg, "Kpatch: Patch is overridden by")?; ++ for uuid in overridden { ++ writeln!(msg, "* Patch '{}'", uuid)?; + } +- err_msg.pop(); +- +- err_msg ++ msg.pop(); ++ msg + }); + Ok(()) + } + } + + impl KernelPatchDriver { +- pub fn status(&self, patch: &KernelPatch) -> Result { +- sys::read_patch_status(patch) +- } +- +- pub fn check(&self, patch: &KernelPatch) -> Result<()> { ++ pub fn check_patch(&self, patch: &KernelPatch) -> Result<()> { + Self::check_consistency(patch)?; + Self::check_compatiblity(patch)?; + Self::check_dependency(patch)?; +- + Ok(()) + } + +- pub fn apply(&mut self, patch: &KernelPatch) -> Result<()> { +- info!( +- "Applying patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); ++ pub fn get_patch_status(&self, patch: &KernelPatch) -> Result { ++ sys::get_patch_status(&patch.status_file).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to get patch status, {}", ++ e.to_string().to_lowercase() ++ ) ++ }) ++ } + ++ pub fn load_patch(&mut self, patch: &KernelPatch) -> Result<()> { + ensure!( + !self.blocked_targets.contains(&patch.target_name), +- "Patch target '{}' is blocked", ++ "Kpatch: Patch target '{}' is blocked", + patch.target_name.to_string_lossy(), + ); +- sys::selinux_relable_patch(patch)?; +- sys::apply_patch(patch)?; +- self.add_patch_target(patch); + +- Ok(()) ++ if selinux::get_status() == selinux::Status::Enforcing { ++ kernel::relable_module_file(&patch.patch_file).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to relable patch file, {}", ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ } ++ sys::load_patch(&patch.patch_file).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to load patch, {}", ++ e.to_string().to_lowercase() ++ ) ++ }) + } + +- pub fn remove(&mut self, patch: &KernelPatch) -> Result<()> { +- info!( +- "Removing patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); +- sys::remove_patch(patch)?; +- self.remove_patch_target(patch); +- +- Ok(()) ++ pub fn remove_patch(&mut self, patch: &KernelPatch) -> Result<()> { ++ sys::remove_patch(&patch.module.name).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to remove patch, {}", ++ e.to_string().to_lowercase() ++ ) ++ }) + } + +- pub fn active(&mut self, patch: &KernelPatch) -> Result<()> { +- info!( +- "Activating patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); +- sys::active_patch(patch)?; +- self.add_patch_functions(patch); ++ pub fn active_patch(&mut self, patch: &KernelPatch) -> Result<()> { ++ sys::active_patch(&patch.status_file).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to active patch, {}", ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ self.register_patch(patch); + + Ok(()) + } + +- pub fn deactive(&mut self, patch: &KernelPatch) -> Result<()> { +- info!( +- "Deactivating patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); +- sys::deactive_patch(patch)?; +- self.remove_patch_functions(patch); ++ pub fn deactive_patch(&mut self, patch: &KernelPatch) -> Result<()> { ++ sys::deactive_patch(&patch.status_file).map_err(|e| { ++ anyhow!( ++ "Kpatch: Failed to deactive patch, {}", ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ self.unregister_patch(patch); + + Ok(()) + } +diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs +index adabfb4..838fe3b 100644 +--- a/syscared/src/patch/driver/kpatch/sys.rs ++++ b/syscared/src/patch/driver/kpatch/sys.rs +@@ -12,106 +12,69 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::{CString, OsString}; ++use std::{ffi::OsStr, fs, path::Path}; + +-use anyhow::{anyhow, bail, Context, Result}; + use log::debug; +-use nix::kmod; ++use nix::errno::Errno; + + use syscare_abi::PatchStatus; +-use syscare_common::{ffi::OsStrExt, fs, os::selinux}; ++use syscare_common::os::kernel; + +-use crate::patch::entity::KernelPatch; ++const KPATCH_STATUS_DISABLE: &str = "0"; ++const KPATCH_STATUS_ENABLE: &str = "1"; + +-const SYS_MODULE_DIR: &str = "/sys/module"; +-const KPATCH_STATUS_DISABLED: &str = "0"; +-const KPATCH_STATUS_ENABLED: &str = "1"; +- +-pub fn list_kernel_modules() -> Result> { +- let module_names = fs::list_dirs(SYS_MODULE_DIR, fs::TraverseOptions { recursive: false })? +- .into_iter() +- .filter_map(|dir| dir.file_name().map(|name| name.to_os_string())) +- .collect(); +- +- Ok(module_names) +-} +- +-pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { +- const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; +- +- if selinux::get_status() != selinux::Status::Enforcing { +- return Ok(()); +- } ++pub fn load_patch>(patch_file: P) -> std::io::Result<()> { ++ let patch_file = patch_file.as_ref(); + + debug!( +- "Relabeling patch module '{}'...", +- patch.module_name.to_string_lossy() ++ "Kpatch: Inserting patch module '{}'...", ++ patch_file.display() + ); +- let mut sec_context = selinux::get_security_context(&patch.patch_file)?; +- if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { +- sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; +- selinux::set_security_context(&patch.patch_file, &sec_context)?; +- } +- +- Ok(()) ++ kernel::insert_module(patch_file) + } + +-pub fn read_patch_status(patch: &KernelPatch) -> Result { +- let sys_file = patch.sys_file.as_path(); +- debug!("Reading {}", sys_file.display()); +- +- let status = match fs::read_to_string(sys_file) { +- Ok(str) => match str.trim() { +- KPATCH_STATUS_DISABLED => Ok(PatchStatus::Deactived), +- KPATCH_STATUS_ENABLED => Ok(PatchStatus::Actived), +- _ => bail!("Kpatch: Invalid patch status"), +- }, +- Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(PatchStatus::NotApplied), +- Err(e) => Err(e), +- } +- .context("Kpatch: Failed to read patch status")?; ++pub fn remove_patch>(module_name: S) -> std::io::Result<()> { ++ let module_name = module_name.as_ref(); + +- Ok(status) ++ debug!( ++ "Kpatch: Removing patch module '{}'...", ++ module_name.to_string_lossy() ++ ); ++ kernel::remove_module(module_name) + } + +-fn write_patch_status(patch: &KernelPatch, value: &str) -> Result<()> { +- let sys_file = patch.sys_file.as_path(); ++pub fn active_patch>(status_file: P) -> std::io::Result<()> { ++ let status_file = status_file.as_ref(); + +- debug!("Writing '{}' to {}", value, sys_file.display()); +- fs::write(sys_file, value).context("Kpatch: Failed to write patch status") +-} +- +-pub fn apply_patch(patch: &KernelPatch) -> Result<()> { + debug!( +- "Inserting patch module '{}'...", +- patch.module_name.to_string_lossy() ++ "Kpatch: Writing '{}' to '{}'...", ++ stringify!(KPATCH_STATUS_ENABLE), ++ status_file.display() + ); +- let patch_module = fs::open_file(&patch.patch_file)?; +- kmod::finit_module( +- &patch_module, +- CString::new("")?.as_c_str(), +- kmod::ModuleInitFlags::empty(), +- ) +- .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) ++ fs::write(status_file, KPATCH_STATUS_ENABLE) + } + +-pub fn remove_patch(patch: &KernelPatch) -> Result<()> { ++pub fn deactive_patch>(status_file: P) -> std::io::Result<()> { ++ let status_file = status_file.as_ref(); ++ + debug!( +- "Removing patch module '{}'...", +- patch.module_name.to_string_lossy() ++ "Kpatch: Writing '{}' to '{}'...", ++ stringify!(KPATCH_STATUS_DISABLE), ++ status_file.display() + ); +- +- kmod::delete_module( +- patch.module_name.to_cstring()?.as_c_str(), +- kmod::DeleteModuleFlags::O_NONBLOCK, +- ) +- .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) ++ fs::write(status_file, KPATCH_STATUS_DISABLE) + } + +-pub fn active_patch(patch: &KernelPatch) -> Result<()> { +- self::write_patch_status(patch, KPATCH_STATUS_ENABLED) +-} ++pub fn get_patch_status>(status_file: P) -> std::io::Result { ++ let status_file = status_file.as_ref(); ++ if !status_file.exists() { ++ return Ok(PatchStatus::NotApplied); ++ } + +-pub fn deactive_patch(patch: &KernelPatch) -> Result<()> { +- self::write_patch_status(patch, KPATCH_STATUS_DISABLED) ++ debug!("Kpatch: Reading '{}'...", status_file.display()); ++ match fs::read_to_string(status_file)?.trim() { ++ KPATCH_STATUS_DISABLE => Ok(PatchStatus::Deactived), ++ KPATCH_STATUS_ENABLE => Ok(PatchStatus::Actived), ++ _ => Err(std::io::Error::from(Errno::EINVAL)), ++ } + } +diff --git a/syscared/src/patch/driver/kpatch/target.rs b/syscared/src/patch/driver/kpatch/target.rs +index 49f1185..3f4a465 100644 +--- a/syscared/src/patch/driver/kpatch/target.rs ++++ b/syscared/src/patch/driver/kpatch/target.rs +@@ -12,126 +12,88 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsString; ++use std::{ ++ collections::{hash_map::Entry, HashMap}, ++ ffi::OsString, ++}; + +-use indexmap::IndexMap; ++use indexmap::IndexSet; + use uuid::Uuid; + +-use crate::patch::entity::KernelPatchFunction; +- +-#[derive(Debug)] +-pub struct PatchFunction { +- pub uuid: Uuid, +- pub name: OsString, +- pub size: u64, +-} +- +-impl PatchFunction { +- fn new(uuid: Uuid, function: &KernelPatchFunction) -> Self { +- Self { +- uuid, +- name: function.name.to_os_string(), +- size: function.new_size, +- } +- } +- +- fn is_same_function(&self, uuid: &Uuid, function: &KernelPatchFunction) -> bool { +- (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) +- } +-} ++use crate::patch::entity::KernelPatch; + + #[derive(Debug)] + pub struct PatchTarget { +- name: OsString, +- function_map: IndexMap>, // function addr -> function collision list ++ object_name: OsString, ++ collision_map: HashMap>, // function name -> patch collision list + } + + impl PatchTarget { +- pub fn new(name: OsString) -> Self { ++ pub fn new(object_name: OsString) -> Self { + Self { +- name, +- function_map: IndexMap::new(), ++ object_name, ++ collision_map: HashMap::new(), + } + } + } + + impl PatchTarget { +- pub fn has_function(&self) -> bool { +- self.function_map.is_empty() +- } +- +- pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) +- where +- I: IntoIterator, +- { +- for function in functions { +- if self.name != function.object { +- continue; ++ pub fn add_patch(&mut self, patch: &KernelPatch) { ++ if let Some(functions) = patch.functions.get(&self.object_name) { ++ for function in functions { ++ self.collision_map ++ .entry(function.name.clone()) ++ .or_default() ++ .insert(patch.uuid); + } +- self.function_map +- .entry(function.name.clone()) +- .or_default() +- .push(PatchFunction::new(uuid, function)); + } + } + +- pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) +- where +- I: IntoIterator, +- { +- for function in functions { +- if self.name != function.object { +- continue; +- } +- if let Some(collision_list) = self.function_map.get_mut(&function.name) { +- if let Some(index) = collision_list +- .iter() +- .position(|patch_function| patch_function.is_same_function(uuid, function)) ++ pub fn remove_patch(&mut self, patch: &KernelPatch) { ++ if let Some(functions) = patch.functions.get(&self.object_name) { ++ for function in functions { ++ if let Entry::Occupied(mut entry) = self.collision_map.entry(function.name.clone()) + { +- collision_list.remove(index); +- if collision_list.is_empty() { +- self.function_map.remove(&function.name); ++ let patch_set = entry.get_mut(); ++ patch_set.shift_remove(&patch.uuid); ++ ++ if patch_set.is_empty() { ++ entry.remove(); + } + } + } + } + } +-} + +-impl PatchTarget { +- pub fn get_conflicts<'a, I>( ++ pub fn is_patched(&self) -> bool { ++ !self.collision_map.is_empty() ++ } ++ ++ pub fn get_conflicted_patches<'a>( + &'a self, +- functions: I, +- ) -> impl IntoIterator +- where +- I: IntoIterator, +- { +- functions.into_iter().filter_map(move |function| { +- if self.name != function.object { +- return None; +- } +- self.function_map +- .get(&function.name) +- .and_then(|list| list.last()) +- }) ++ patch: &'a KernelPatch, ++ ) -> impl Iterator + 'a { ++ let functions = patch.functions.get(&self.object_name).into_iter().flatten(); ++ functions ++ .filter_map(move |function| self.collision_map.get(&function.name)) ++ .flatten() ++ .copied() ++ .filter(move |&uuid| uuid != patch.uuid) + } + +- pub fn get_overrides<'a, I>( ++ pub fn get_overridden_patches<'a>( + &'a self, +- uuid: &'a Uuid, +- functions: I, +- ) -> impl IntoIterator +- where +- I: IntoIterator, +- { +- functions.into_iter().filter_map(move |function| { +- if self.name != function.object { +- return None; +- } +- self.function_map +- .get(&function.name) +- .and_then(|list| list.last()) +- .filter(|patch_function| !patch_function.is_same_function(uuid, function)) +- }) ++ patch: &'a KernelPatch, ++ ) -> impl Iterator + 'a { ++ let functions = patch.functions.get(&self.object_name).into_iter().flatten(); ++ functions ++ .filter_map(move |function| self.collision_map.get(&function.name)) ++ .flat_map(move |collision_list| { ++ collision_list ++ .iter() ++ .copied() ++ .skip_while(move |&uuid| uuid != patch.uuid) ++ .skip(1) ++ }) + } + } +diff --git a/syscared/src/patch/driver/mod.rs b/syscared/src/patch/driver/mod.rs +index 707bf2f..3752ca8 100644 +--- a/syscared/src/patch/driver/mod.rs ++++ b/syscared/src/patch/driver/mod.rs +@@ -14,7 +14,7 @@ + + use anyhow::{Context, Result}; + +-use log::info; ++use log::{debug, info}; + use syscare_abi::PatchStatus; + + mod kpatch; +@@ -39,17 +39,17 @@ pub struct PatchDriver { + } + + impl PatchDriver { +- fn check_conflict_functions(&self, patch: &Patch) -> Result<()> { ++ fn check_conflicted_patches(&self, patch: &Patch) -> Result<()> { + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.check_conflict_functions(kpatch), +- Patch::UserPatch(upatch) => self.upatch.check_conflict_functions(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.check_conflicted_patches(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.check_conflicted_patches(upatch), + } + } + +- fn check_override_functions(&self, patch: &Patch) -> Result<()> { ++ fn check_overridden_patches(&self, patch: &Patch) -> Result<()> { + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.check_override_functions(kpatch), +- Patch::UserPatch(upatch) => self.upatch.check_override_functions(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.check_overridden_patches(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.check_overridden_patches(upatch), + } + } + } +@@ -70,54 +70,62 @@ impl PatchDriver { + }) + } + +- /// Fetch and return the patch status. +- pub fn patch_status(&self, patch: &Patch) -> Result { +- match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.status(kpatch), +- Patch::UserPatch(upatch) => self.upatch.status(upatch), ++ /// Perform patch confliction check.
++ /// Used for patch check. ++ pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { ++ if flag == PatchOpFlag::Force { ++ return Ok(()); + } +- .with_context(|| format!("Failed to get patch '{}' status", patch)) ++ self.check_conflicted_patches(patch) ++ .with_context(|| format!("Patch '{}' is conflicted", patch)) + } + + /// Perform patch file intergrity & consistency check.
+ /// Should be used before patch application. + pub fn check_patch(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { ++ info!("Checking patch '{}'...", patch); ++ + if flag == PatchOpFlag::Force { + return Ok(()); + } + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.check(kpatch), +- Patch::UserPatch(upatch) => self.upatch.check(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.check_patch(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.check_patch(upatch), + } + .with_context(|| format!("Patch '{}' is not patchable", patch)) + } + +- /// Perform patch confliction check.
+- /// Used for patch check. +- pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { +- if flag == PatchOpFlag::Force { +- return Ok(()); ++ /// Fetch and return the patch status. ++ pub fn get_patch_status(&self, patch: &Patch) -> Result { ++ debug!("Fetching patch '{}' status...", patch); ++ ++ match patch { ++ Patch::KernelPatch(kpatch) => self.kpatch.get_patch_status(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.get_patch_status(upatch), + } +- self.check_conflict_functions(patch) +- .with_context(|| format!("Patch '{}' is conflicted", patch)) ++ .with_context(|| format!("Failed to get patch '{}' status", patch)) + } + +- /// Apply a patch.
++ /// Load a patch.
+ /// After this action, the patch status would be changed to 'DEACTIVED'. +- pub fn apply_patch(&mut self, patch: &Patch) -> Result<()> { ++ pub fn load_patch(&mut self, patch: &Patch) -> Result<()> { ++ info!("Loading patch '{}'...", patch); ++ + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.apply(kpatch), +- Patch::UserPatch(upatch) => self.upatch.apply(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.load_patch(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.load_patch(upatch), + } +- .with_context(|| format!("Failed to apply patch '{}'", patch)) ++ .with_context(|| format!("Failed to load patch '{}'", patch)) + } + + /// Remove a patch.
+ /// After this action, the patch status would be changed to 'NOT-APPLIED'. + pub fn remove_patch(&mut self, patch: &Patch) -> Result<()> { ++ info!("Removing patch '{}'...", patch); ++ + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.remove(kpatch), +- Patch::UserPatch(upatch) => self.upatch.remove(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.remove_patch(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.remove_patch(upatch), + } + .with_context(|| format!("Failed to remove patch '{}'", patch)) + } +@@ -125,12 +133,15 @@ impl PatchDriver { + /// Active a patch.
+ /// After this action, the patch status would be changed to 'ACTIVED'. + pub fn active_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { ++ info!("Activating patch '{}'...", patch); ++ + if flag != PatchOpFlag::Force { +- self.check_conflict_functions(patch)?; ++ self.check_conflicted_patches(patch)?; + } ++ + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.active(kpatch), +- Patch::UserPatch(upatch) => self.upatch.active(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.active_patch(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.active_patch(upatch), + } + .with_context(|| format!("Failed to active patch '{}'", patch)) + } +@@ -138,12 +149,15 @@ impl PatchDriver { + /// Deactive a patch.
+ /// After this action, the patch status would be changed to 'DEACTIVED'. + pub fn deactive_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { ++ info!("Deactivating patch '{}'...", patch); ++ + if flag != PatchOpFlag::Force { +- self.check_override_functions(patch)?; ++ self.check_overridden_patches(patch)?; + } ++ + match patch { +- Patch::KernelPatch(kpatch) => self.kpatch.deactive(kpatch), +- Patch::UserPatch(upatch) => self.upatch.deactive(upatch), ++ Patch::KernelPatch(kpatch) => self.kpatch.deactive_patch(kpatch), ++ Patch::UserPatch(upatch) => self.upatch.deactive_patch(upatch), + } + .with_context(|| format!("Failed to deactive patch '{}'", patch)) + } +diff --git a/syscared/src/patch/driver/upatch/entity.rs b/syscared/src/patch/driver/upatch/entity.rs +deleted file mode 100644 +index 5d0a9c0..0000000 +--- a/syscared/src/patch/driver/upatch/entity.rs ++++ /dev/null +@@ -1,60 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscared is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::path::PathBuf; +- +-use indexmap::{indexset, IndexSet}; +- +-#[derive(Debug)] +-pub struct PatchEntity { +- pub patch_file: PathBuf, +- process_list: IndexSet, +-} +- +-impl PatchEntity { +- pub fn new(patch_file: PathBuf) -> Self { +- Self { +- patch_file, +- process_list: indexset! {}, +- } +- } +-} +- +-impl PatchEntity { +- pub fn add_process(&mut self, pid: i32) { +- self.process_list.insert(pid); +- } +- +- pub fn remove_process(&mut self, pid: i32) { +- self.process_list.remove(&pid); +- } +- +- pub fn clean_dead_process(&mut self, process_list: &IndexSet) { +- self.process_list.retain(|pid| process_list.contains(pid)); +- } +- +- pub fn need_actived(&self, process_list: &IndexSet) -> IndexSet { +- process_list +- .difference(&self.process_list) +- .copied() +- .collect() +- } +- +- pub fn need_deactived(&self, process_list: &IndexSet) -> IndexSet { +- process_list +- .intersection(&self.process_list) +- .copied() +- .collect() +- } +-} +diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs +index c31ef78..cc00f53 100644 +--- a/syscared/src/patch/driver/upatch/mod.rs ++++ b/syscared/src/patch/driver/upatch/mod.rs +@@ -13,6 +13,7 @@ + */ + + use std::{ ++ collections::{HashMap, HashSet}, + ffi::OsStr, + fmt::Write, + iter::FromIterator, +@@ -21,21 +22,16 @@ use std::{ + sync::Arc, + }; + +-use anyhow::{bail, ensure, Context, Result}; +-use indexmap::{indexset, IndexMap, IndexSet}; +-use log::{debug, info, warn}; ++use anyhow::{bail, ensure, Result}; ++use log::{debug, warn}; + use parking_lot::RwLock; + use uuid::Uuid; + + use syscare_abi::PatchStatus; + use syscare_common::{fs, util::digest}; + +-use crate::{ +- config::UserPatchConfig, +- patch::{driver::upatch::entity::PatchEntity, entity::UserPatch}, +-}; ++use crate::{config::UserPatchConfig, patch::entity::UserPatch}; + +-mod entity; + mod monitor; + mod sys; + mod target; +@@ -44,19 +40,19 @@ use monitor::UserPatchMonitor; + use target::PatchTarget; + + pub struct UserPatchDriver { +- status_map: IndexMap, +- target_map: Arc>>, +- skipped_files: Arc>, ++ status_map: HashMap, ++ target_map: Arc>>, ++ skipped_files: Arc>, + monitor: UserPatchMonitor, + } + + impl UserPatchDriver { + pub fn new(config: &UserPatchConfig) -> Result { +- let target_map = Arc::new(RwLock::new(IndexMap::new())); +- let skipped_files = Arc::new(IndexSet::from_iter(config.skipped.iter().cloned())); ++ let target_map = Arc::new(RwLock::new(HashMap::new())); ++ let skipped_files = Arc::new(HashSet::from_iter(config.skipped.iter().cloned())); + + Ok(Self { +- status_map: IndexMap::new(), ++ status_map: HashMap::new(), + target_map: target_map.clone(), + skipped_files: skipped_files.clone(), + monitor: UserPatchMonitor::new(move |target_elfs| { +@@ -70,7 +66,7 @@ impl UserPatchDriver { + + impl UserPatchDriver { + #[inline] +- fn get_patch_status(&self, uuid: &Uuid) -> PatchStatus { ++ fn read_patch_status(&self, uuid: &Uuid) -> PatchStatus { + self.status_map + .get(uuid) + .copied() +@@ -78,7 +74,7 @@ impl UserPatchDriver { + } + + #[inline] +- fn set_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { ++ fn write_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { + *self.status_map.entry(*uuid).or_default() = value; + } + +@@ -87,28 +83,6 @@ impl UserPatchDriver { + } + } + +-impl UserPatchDriver { +- fn add_patch_target(&mut self, patch: &UserPatch) { +- let target_elf = patch.target_elf.as_path(); +- let mut target_map = self.target_map.write(); +- +- if !target_map.contains_key(target_elf) { +- target_map.insert(target_elf.to_path_buf(), PatchTarget::default()); +- } +- } +- +- fn remove_patch_target(&mut self, patch: &UserPatch) { +- let target_elf = patch.target_elf.as_path(); +- let mut target_map = self.target_map.write(); +- +- if let Some(target) = target_map.get_mut(target_elf) { +- if !target.is_patched() { +- target_map.remove(target_elf); +- } +- } +- } +-} +- + impl UserPatchDriver { + fn check_consistency(patch: &UserPatch) -> Result<()> { + let real_checksum = digest::file(&patch.patch_file)?; +@@ -122,56 +96,39 @@ impl UserPatchDriver { + Ok(()) + } + +- fn check_compatiblity(_patch: &UserPatch) -> Result<()> { +- Ok(()) +- } +- +- pub fn check_conflict_functions(&self, patch: &UserPatch) -> Result<()> { +- let conflict_patches = match self.target_map.read().get(&patch.target_elf) { +- Some(target) => target +- .get_conflicts(&patch.functions) +- .into_iter() +- .map(|record| record.uuid) +- .collect(), +- None => indexset! {}, ++ pub fn check_conflicted_patches(&self, patch: &UserPatch) -> Result<()> { ++ let conflicted = match self.target_map.read().get(&patch.target_elf) { ++ Some(target) => target.get_conflicted_patches(patch).collect(), ++ None => HashSet::new(), + }; + +- ensure!(conflict_patches.is_empty(), { +- let mut err_msg = String::new(); +- +- writeln!(&mut err_msg, "Upatch: Patch is conflicted with")?; +- for uuid in conflict_patches.into_iter() { +- writeln!(&mut err_msg, "* Patch '{}'", uuid)?; ++ ensure!(conflicted.is_empty(), { ++ let mut msg = String::new(); ++ writeln!(msg, "Upatch: Patch is conflicted with")?; ++ for uuid in conflicted.into_iter() { ++ writeln!(msg, "* Patch '{}'", uuid)?; + } +- err_msg.pop(); +- +- err_msg ++ msg.pop(); ++ msg + }); + Ok(()) + } + +- pub fn check_override_functions(&self, patch: &UserPatch) -> Result<()> { +- let override_patches = match self.target_map.read().get(&patch.target_elf) { +- Some(target) => target +- .get_overrides(&patch.uuid, &patch.functions) +- .into_iter() +- .map(|record| record.uuid) +- .collect(), +- None => indexset! {}, ++ pub fn check_overridden_patches(&self, patch: &UserPatch) -> Result<()> { ++ let overridden = match self.target_map.read().get(&patch.target_elf) { ++ Some(target) => target.get_overridden_patches(patch).collect(), ++ None => HashSet::new(), + }; + +- ensure!(override_patches.is_empty(), { +- let mut err_msg = String::new(); +- +- writeln!(&mut err_msg, "Upatch: Patch is overrided by")?; +- for uuid in override_patches.into_iter() { +- writeln!(&mut err_msg, "* Patch '{}'", uuid)?; ++ ensure!(overridden.is_empty(), { ++ let mut msg = String::new(); ++ writeln!(msg, "Upatch: Patch is overridden by")?; ++ for uuid in overridden.into_iter() { ++ writeln!(msg, "* Patch '{}'", uuid)?; + } +- err_msg.pop(); +- +- err_msg ++ msg.pop(); ++ msg + }); +- + Ok(()) + } + } +@@ -187,10 +144,10 @@ impl UserPatchDriver { + } + + fn find_target_process>( +- skipped_files: &IndexSet, ++ skipped_files: &HashSet, + target_elf: P, +- ) -> Result> { +- let mut target_pids = IndexSet::new(); ++ ) -> Result> { ++ let mut target_pids = HashSet::new(); + let target_path = target_elf.as_ref(); + let target_inode = target_path.metadata()?.st_ino(); + +@@ -233,8 +190,8 @@ impl UserPatchDriver { + } + + fn patch_new_process( +- target_map: &RwLock>, +- skipped_files: &IndexSet, ++ target_map: &RwLock>, ++ skipped_files: &HashSet, + target_elf: &Path, + ) { + let process_list = match Self::find_target_process(skipped_files, target_elf) { +@@ -242,33 +199,32 @@ impl UserPatchDriver { + Err(_) => return, + }; + +- let mut patch_target_map = target_map.write(); +- let patch_target = match patch_target_map.get_mut(target_elf) { ++ let mut target_map = target_map.write(); ++ let patch_target = match target_map.get_mut(target_elf) { + Some(target) => target, + None => return, + }; ++ patch_target.clean_dead_process(&process_list); + +- for (patch_uuid, patch_entity) in patch_target.all_patches() { +- patch_entity.clean_dead_process(&process_list); ++ let all_patches = patch_target.all_patches().collect::>(); ++ let need_actived = patch_target.need_actived(&process_list); + +- // Active patch +- let need_actived = patch_entity.need_actived(&process_list); ++ for (uuid, patch_file) in all_patches { + if !need_actived.is_empty() { + debug!( +- "Activating patch '{}' ({}) for process {:?}", +- patch_uuid, ++ "Upatch: Activating patch '{}' ({}) for process {:?}", ++ uuid, + target_elf.display(), + need_actived, + ); + } +- +- for pid in need_actived { +- match sys::active_patch(patch_uuid, pid, target_elf, &patch_entity.patch_file) { +- Ok(_) => patch_entity.add_process(pid), ++ for &pid in &need_actived { ++ match sys::active_patch(&uuid, pid, target_elf, &patch_file) { ++ Ok(_) => patch_target.add_process(pid), + Err(e) => { + warn!( + "Upatch: Failed to active patch '{}' for process {}, {}", +- patch_uuid, ++ uuid, + pid, + e.to_string().to_lowercase(), + ); +@@ -280,198 +236,140 @@ impl UserPatchDriver { + } + + impl UserPatchDriver { +- pub fn status(&self, patch: &UserPatch) -> Result { +- Ok(self.get_patch_status(&patch.uuid)) +- } +- +- pub fn check(&self, patch: &UserPatch) -> Result<()> { ++ pub fn check_patch(&self, patch: &UserPatch) -> Result<()> { + Self::check_consistency(patch)?; +- Self::check_compatiblity(patch)?; +- + Ok(()) + } + +- pub fn apply(&mut self, patch: &UserPatch) -> Result<()> { +- info!( +- "Applying patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); +- +- self.add_patch_target(patch); +- self.set_patch_status(&patch.uuid, PatchStatus::Deactived); ++ pub fn get_patch_status(&self, patch: &UserPatch) -> Result { ++ Ok(self.read_patch_status(&patch.uuid)) ++ } + ++ pub fn load_patch(&mut self, patch: &UserPatch) -> Result<()> { ++ self.write_patch_status(&patch.uuid, PatchStatus::Deactived); + Ok(()) + } + +- pub fn remove(&mut self, patch: &UserPatch) -> Result<()> { +- info!( +- "Removing patch '{}' ({})", +- patch.uuid, +- patch.patch_file.display() +- ); +- +- self.remove_patch_target(patch); ++ pub fn remove_patch(&mut self, patch: &UserPatch) -> Result<()> { + self.remove_patch_status(&patch.uuid); +- + Ok(()) + } + +- pub fn active(&mut self, patch: &UserPatch) -> Result<()> { +- let patch_uuid = &patch.uuid; +- let patch_file = patch.patch_file.as_path(); +- let patch_functions = patch.functions.as_slice(); +- let target_elf = patch.target_elf.as_path(); +- +- let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; ++ pub fn active_patch(&mut self, patch: &UserPatch) -> Result<()> { ++ let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; + + let mut target_map = self.target_map.write(); +- let patch_target = target_map +- .get_mut(target_elf) +- .context("Upatch: Cannot find patch target")?; +- let mut patch_entity = match patch_target.get_patch(patch_uuid) { +- Some(_) => bail!("Upatch: Patch is already exist"), +- None => PatchEntity::new(patch_file.to_path_buf()), +- }; ++ let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); ++ patch_target.clean_dead_process(&process_list); ++ ++ // If target is not patched before, start watching it ++ let start_watch = !patch_target.is_patched(); + + // Active patch +- info!( +- "Activating patch '{}' ({}) for {}", +- patch_uuid, +- patch_file.display(), +- target_elf.display(), +- ); ++ let need_actived = patch_target.need_actived(&process_list); ++ + let mut results = Vec::new(); +- for pid in patch_entity.need_actived(&process_list) { +- let result = sys::active_patch(patch_uuid, pid, target_elf, patch_file); +- if result.is_ok() { +- patch_entity.add_process(pid); +- } ++ for pid in need_actived { ++ let result = sys::active_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); + results.push((pid, result)); + } + +- // Check results, return error if all process fails ++ // Return error if all process fails + if !results.is_empty() && results.iter().all(|(_, result)| result.is_err()) { +- let mut err_msg = String::new(); +- +- writeln!(err_msg, "Upatch: Failed to active patch")?; ++ let mut msg = String::new(); ++ writeln!(msg, "Upatch: Failed to active patch")?; + for (pid, result) in &results { + if let Err(e) = result { +- writeln!(err_msg, "* Process {}: {}", pid, e)?; ++ writeln!(msg, "* Process {}: {}", pid, e)?; + } + } +- err_msg.pop(); +- bail!(err_msg); ++ msg.pop(); ++ bail!(msg); + } + +- // Print failure results +- for (pid, result) in &results { +- if let Err(e) = result { +- warn!( +- "Upatch: Failed to active patch '{}' for process {}, {}", +- patch_uuid, +- pid, +- e.to_string().to_lowercase(), +- ); ++ // Process results ++ for (pid, result) in results { ++ match result { ++ Ok(_) => patch_target.add_process(pid), ++ Err(e) => { ++ warn!( ++ "Upatch: Failed to active patch '{}' for process {}, {}", ++ patch.uuid, ++ pid, ++ e.to_string().to_lowercase(), ++ ); ++ } + } + } +- +- // If target is no patched before, start watching it +- let need_start_watch = !patch_target.is_patched(); +- +- // Apply patch to target +- patch_target.add_patch(*patch_uuid, patch_entity); +- patch_target.add_functions(*patch_uuid, patch_functions); ++ patch_target.add_patch(patch); + + // Drop the lock + drop(target_map); + +- if need_start_watch { +- self.monitor.watch_file(target_elf)?; ++ if start_watch { ++ self.monitor.watch_file(&patch.target_elf)?; + } +- self.set_patch_status(patch_uuid, PatchStatus::Actived); + ++ self.write_patch_status(&patch.uuid, PatchStatus::Actived); + Ok(()) + } + +- pub fn deactive(&mut self, patch: &UserPatch) -> Result<()> { +- let patch_uuid = &patch.uuid; +- let patch_file = patch.patch_file.as_path(); +- let patch_functions = patch.functions.as_slice(); +- let target_elf = patch.target_elf.as_path(); +- +- let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; ++ pub fn deactive_patch(&mut self, patch: &UserPatch) -> Result<()> { ++ let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; + + let mut target_map = self.target_map.write(); +- let patch_target = target_map +- .get_mut(target_elf) +- .context("Upatch: Cannot find patch target")?; +- let patch_entity = patch_target +- .get_patch(patch_uuid) +- .context("Upatch: Cannot find patch entity")?; +- +- // Remove dead process +- patch_entity.clean_dead_process(&process_list); ++ let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); ++ patch_target.clean_dead_process(&process_list); + + // Deactive patch +- info!( +- "Deactivating patch '{}' ({}) for {}", +- patch_uuid, +- patch_file.display(), +- target_elf.display(), +- ); +- +- let need_deactived = patch_entity.need_deactived(&process_list); ++ let need_deactive = patch_target.need_deactived(&process_list); + + let mut results = Vec::new(); +- for pid in need_deactived { +- let result = sys::deactive_patch(patch_uuid, pid, target_elf, patch_file); +- if result.is_ok() { +- patch_entity.remove_process(pid) +- } ++ for pid in need_deactive { ++ let result = ++ sys::deactive_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); + results.push((pid, result)); + } + +- // Check results, return error if any process failes ++ // Return error if all process fails + if !results.is_empty() && results.iter().any(|(_, result)| result.is_err()) { +- let mut err_msg = String::new(); +- +- writeln!(err_msg, "Upatch: Failed to deactive patch")?; ++ let mut msg = String::new(); ++ writeln!(msg, "Upatch: Failed to deactive patch")?; + for (pid, result) in &results { + if let Err(e) = result { +- writeln!(err_msg, "* Process {}: {}", pid, e)?; ++ writeln!(msg, "* Process {}: {}", pid, e)?; + } + } +- err_msg.pop(); +- bail!(err_msg); ++ msg.pop(); ++ bail!(msg); + } + +- // Print failure results +- for (pid, result) in &results { +- if let Err(e) = result { +- warn!( +- "Upatch: Failed to deactive patch '{}' for process {}, {}", +- patch_uuid, +- pid, +- e.to_string().to_lowercase(), +- ); ++ // Process results ++ for (pid, result) in results { ++ match result { ++ Ok(_) => patch_target.remove_process(pid), ++ Err(e) => { ++ warn!( ++ "Upatch: Failed to deactive patch '{}' for process {}, {}", ++ patch.uuid, ++ pid, ++ e.to_string().to_lowercase(), ++ ); ++ } + } + } ++ patch_target.remove_patch(patch); + +- // Remove patch functions from target +- patch_target.remove_patch(patch_uuid); +- patch_target.remove_functions(patch_uuid, patch_functions); +- +- // If target is no longer patched, stop watching it +- let need_stop_watch = !patch_target.is_patched(); ++ // If target is no longer has patch, stop watching it ++ let stop_watch = !patch_target.is_patched(); + + drop(target_map); + +- if need_stop_watch { +- self.monitor.ignore_file(target_elf)?; ++ if stop_watch { ++ self.monitor.ignore_file(&patch.target_elf)?; + } +- self.set_patch_status(patch_uuid, PatchStatus::Deactived); + ++ self.write_patch_status(&patch.uuid, PatchStatus::Deactived); + Ok(()) + } + } +diff --git a/syscared/src/patch/driver/upatch/monitor.rs b/syscared/src/patch/driver/upatch/monitor.rs +index c0c1b3c..150d1a3 100644 +--- a/syscared/src/patch/driver/upatch/monitor.rs ++++ b/syscared/src/patch/driver/upatch/monitor.rs +@@ -20,7 +20,7 @@ use std::{ + time::Duration, + }; + +-use anyhow::{bail, Context, Result}; ++use anyhow::{anyhow, bail, Context, Result}; + use indexmap::IndexMap; + use inotify::{Inotify, WatchDescriptor, WatchMask}; + use log::info; +@@ -75,13 +75,19 @@ impl UserPatchMonitor { + Some(inotify) => { + let wd = inotify + .add_watch(watch_file, WatchMask::OPEN) +- .with_context(|| format!("Failed to watch file {}", watch_file.display()))?; ++ .map_err(|e| { ++ anyhow!( ++ "Failed to watch file '{}', {}", ++ watch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; + + self.watch_file_map + .write() + .insert(wd.clone(), watch_file.to_owned()); + self.watch_wd_map.lock().insert(watch_file.to_owned(), wd); +- info!("Start watching file {}", watch_file.display()); ++ info!("Start watching file '{}'", watch_file.display()); + } + None => bail!("Inotify does not exist"), + } +@@ -97,10 +103,14 @@ impl UserPatchMonitor { + Some(inotify) => { + self.watch_file_map.write().remove(&wd); + +- inotify.rm_watch(wd).with_context(|| { +- format!("Failed to stop watch file {}", ignore_file.display()) ++ inotify.rm_watch(wd).map_err(|e| { ++ anyhow!( ++ "Failed to stop watch file '{}', {}", ++ ignore_file.display(), ++ e.to_string().to_lowercase() ++ ) + })?; +- info!("Stop watching file {}", ignore_file.display()); ++ info!("Stop watching file '{}'", ignore_file.display()); + } + None => bail!("Inotify does not exist"), + } +@@ -124,7 +134,13 @@ where + thread::Builder::new() + .name(MONITOR_THREAD_NAME.to_string()) + .spawn(move || self.thread_main()) +- .with_context(|| format!("Failed to create thread '{}'", MONITOR_THREAD_NAME)) ++ .map_err(|e| { ++ anyhow!( ++ "Failed to create thread '{}', {}", ++ MONITOR_THREAD_NAME, ++ e.to_string().to_lowercase() ++ ) ++ }) + } + + #[inline] +diff --git a/syscared/src/patch/driver/upatch/sys.rs b/syscared/src/patch/driver/upatch/sys.rs +index ecc0522..8518840 100644 +--- a/syscared/src/patch/driver/upatch/sys.rs ++++ b/syscared/src/patch/driver/upatch/sys.rs +@@ -1,14 +1,27 @@ + use std::path::Path; + + use anyhow::{bail, Result}; +-use log::Level; ++use log::{debug, Level}; + use uuid::Uuid; + + use syscare_common::process::Command; + + const UPATCH_MANAGE_BIN: &str = "upatch-manage"; + +-pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { ++pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> ++where ++ P: AsRef, ++ Q: AsRef, ++{ ++ let target_elf = target_elf.as_ref(); ++ let patch_file = patch_file.as_ref(); ++ ++ debug!( ++ "Upatch: Patching '{}' to '{}' (pid: {})...", ++ patch_file.display(), ++ target_elf.display(), ++ pid, ++ ); + let exit_code = Command::new(UPATCH_MANAGE_BIN) + .arg("patch") + .arg("--uuid") +@@ -29,7 +42,20 @@ pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) + } + } + +-pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { ++pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> ++where ++ P: AsRef, ++ Q: AsRef, ++{ ++ let target_elf = target_elf.as_ref(); ++ let patch_file = patch_file.as_ref(); ++ ++ debug!( ++ "Upatch: Unpatching '{}' from '{}' (pid: {})...", ++ patch_file.display(), ++ target_elf.display(), ++ pid, ++ ); + let exit_code = Command::new(UPATCH_MANAGE_BIN) + .arg("unpatch") + .arg("--uuid") +diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs +index 26c3ed3..e0e9f88 100644 +--- a/syscared/src/patch/driver/upatch/target.rs ++++ b/syscared/src/patch/driver/upatch/target.rs +@@ -12,125 +12,113 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::ffi::OsString; ++use std::{ ++ collections::{hash_map::Entry, HashMap, HashSet}, ++ path::PathBuf, ++}; + +-use indexmap::IndexMap; ++use indexmap::IndexSet; + use uuid::Uuid; + +-use crate::patch::entity::UserPatchFunction; +- +-use super::entity::PatchEntity; +- +-#[derive(Debug)] +-pub struct PatchFunction { +- pub uuid: Uuid, +- pub name: OsString, +- pub size: u64, +-} +- +-impl PatchFunction { +- pub fn new(uuid: Uuid, function: &UserPatchFunction) -> Self { +- Self { +- uuid, +- name: function.name.to_os_string(), +- size: function.new_size, +- } +- } +- +- pub fn is_same_function(&self, uuid: &Uuid, function: &UserPatchFunction) -> bool { +- (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) +- } +-} ++use crate::patch::entity::UserPatch; + + #[derive(Debug, Default)] + pub struct PatchTarget { +- patch_map: IndexMap, // patched file data +- function_map: IndexMap>, // function addr -> function collision list ++ process_list: HashSet, ++ patch_map: HashMap, // uuid -> patch file ++ collision_map: HashMap>, // function old addr -> patch collision list + } + + impl PatchTarget { +- pub fn is_patched(&self) -> bool { +- !self.patch_map.is_empty() ++ pub fn add_process(&mut self, pid: i32) { ++ self.process_list.insert(pid); + } + +- pub fn add_patch(&mut self, uuid: Uuid, entity: PatchEntity) { +- self.patch_map.insert(uuid, entity); ++ pub fn remove_process(&mut self, pid: i32) { ++ self.process_list.remove(&pid); + } + +- pub fn remove_patch(&mut self, uuid: &Uuid) { +- self.patch_map.remove(uuid); ++ pub fn clean_dead_process(&mut self, process_list: &HashSet) { ++ self.process_list.retain(|pid| process_list.contains(pid)); + } + +- pub fn get_patch(&mut self, uuid: &Uuid) -> Option<&mut PatchEntity> { +- self.patch_map.get_mut(uuid) ++ pub fn need_actived(&self, process_list: &HashSet) -> HashSet { ++ process_list ++ .difference(&self.process_list) ++ .copied() ++ .collect() + } + +- pub fn all_patches(&mut self) -> impl IntoIterator { +- self.patch_map.iter_mut() ++ pub fn need_deactived(&self, process_list: &HashSet) -> HashSet { ++ process_list ++ .intersection(&self.process_list) ++ .copied() ++ .collect() + } + } + + impl PatchTarget { +- pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) +- where +- I: IntoIterator, +- { +- for function in functions { +- self.function_map ++ pub fn add_patch(&mut self, patch: &UserPatch) { ++ for function in &patch.functions { ++ self.collision_map + .entry(function.old_addr) + .or_default() +- .push(PatchFunction::new(uuid, function)); ++ .insert(patch.uuid); + } ++ self.patch_map.insert(patch.uuid, patch.patch_file.clone()); + } + +- pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) +- where +- I: IntoIterator, +- { +- for function in functions { +- if let Some(collision_list) = self.function_map.get_mut(&function.old_addr) { +- if let Some(index) = collision_list +- .iter() +- .position(|patch_function| patch_function.is_same_function(uuid, function)) +- { +- collision_list.remove(index); +- if collision_list.is_empty() { +- self.function_map.remove(&function.old_addr); +- } ++ pub fn remove_patch(&mut self, patch: &UserPatch) { ++ for function in &patch.functions { ++ if let Entry::Occupied(mut entry) = self.collision_map.entry(function.old_addr) { ++ let patch_set = entry.get_mut(); ++ patch_set.shift_remove(&patch.uuid); ++ ++ if patch_set.is_empty() { ++ entry.remove(); + } + } + } ++ self.patch_map.remove(&patch.uuid); + } +-} + +-impl PatchTarget { +- pub fn get_conflicts<'a, I>( ++ pub fn is_patched(&self) -> bool { ++ !self.collision_map.is_empty() ++ } ++ ++ pub fn all_patches(&self) -> impl Iterator + '_ { ++ self.patch_map ++ .iter() ++ .map(|(uuid, path)| (*uuid, path.to_path_buf())) ++ } ++ ++ pub fn get_conflicted_patches<'a>( + &'a self, +- functions: I, +- ) -> impl IntoIterator +- where +- I: IntoIterator, +- { +- functions.into_iter().filter_map(move |function| { +- self.function_map +- .get(&function.old_addr) +- .and_then(|list| list.last()) +- }) ++ patch: &'a UserPatch, ++ ) -> impl Iterator + 'a { ++ patch ++ .functions ++ .iter() ++ .filter_map(move |function| self.collision_map.get(&function.old_addr)) ++ .flatten() ++ .copied() ++ .filter(move |&uuid| uuid != patch.uuid) + } + +- pub fn get_overrides<'a, I>( ++ pub fn get_overridden_patches<'a>( + &'a self, +- uuid: &'a Uuid, +- functions: I, +- ) -> impl IntoIterator +- where +- I: IntoIterator, +- { +- functions.into_iter().filter_map(move |function| { +- self.function_map +- .get(&function.old_addr) +- .and_then(|list| list.last()) +- .filter(|patch_function| !patch_function.is_same_function(uuid, function)) +- }) ++ patch: &'a UserPatch, ++ ) -> impl Iterator + 'a { ++ patch ++ .functions ++ .iter() ++ .filter_map(move |function| self.collision_map.get(&function.old_addr)) ++ .flat_map(move |collision_list| { ++ collision_list ++ .iter() ++ .copied() ++ .skip_while(move |&uuid| uuid != patch.uuid) ++ .skip(1) ++ }) + } + } +diff --git a/syscared/src/patch/entity/kpatch.rs b/syscared/src/patch/entity/kpatch.rs +index 4c0be9c..09e759d 100644 +--- a/syscared/src/patch/entity/kpatch.rs ++++ b/syscared/src/patch/entity/kpatch.rs +@@ -12,11 +12,78 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{ffi::OsString, path::PathBuf, sync::Arc}; ++use std::{ ++ collections::HashMap, ++ ffi::{CStr, OsStr, OsString}, ++ path::{Path, PathBuf}, ++ sync::Arc, ++}; + +-use syscare_abi::PatchInfo; ++use anyhow::{anyhow, Context, Result}; ++use object::{File, Object, ObjectSection}; + use uuid::Uuid; + ++use syscare_abi::{PatchEntity, PatchInfo}; ++use syscare_common::{ffi::CStrExt, fs, os::kernel}; ++ ++mod ffi { ++ use std::os::raw::{c_char, c_long, c_ulong}; ++ ++ use object::{Pod, Relocation, SectionRelocationIterator}; ++ ++ #[repr(C)] ++ #[derive(Debug, Clone, Copy)] ++ /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` ++ pub struct KpatchFunction { ++ pub new_addr: c_ulong, ++ pub new_size: c_ulong, ++ pub old_addr: c_ulong, ++ pub old_size: c_ulong, ++ pub sympos: u64, ++ pub name: *const c_char, ++ pub obj_name: *const c_char, ++ pub ref_name: *const c_char, ++ pub ref_offset: c_long, ++ } ++ ++ pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); ++ pub const KPATCH_FUNCTION_OFFSET: usize = 40; ++ pub const KPATCH_OBJECT_OFFSET: usize = 48; ++ ++ /* ++ * SAFETY: This struct is ++ * - #[repr(C)] ++ * - have no invalid byte values ++ * - have no padding ++ */ ++ unsafe impl Pod for KpatchFunction {} ++ ++ pub struct KpatchRelocation { ++ pub _addr: (u64, Relocation), ++ pub name: (u64, Relocation), ++ pub object: (u64, Relocation), ++ } ++ ++ pub struct KpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); ++ ++ impl Iterator for KpatchRelocationIterator<'_, '_> { ++ type Item = KpatchRelocation; ++ ++ fn next(&mut self) -> Option { ++ if let (Some(addr), Some(name), Some(object)) = ++ (self.0.next(), self.0.next(), self.0.next()) ++ { ++ return Some(KpatchRelocation { ++ _addr: addr, ++ name, ++ object, ++ }); ++ } ++ None ++ } ++ } ++} ++ + /// Kernel patch function definition + #[derive(Clone)] + pub struct KernelPatchFunction { +@@ -63,10 +130,157 @@ pub struct KernelPatch { + pub name: OsString, + pub info: Arc, + pub pkg_name: String, +- pub module_name: OsString, + pub target_name: OsString, +- pub functions: Vec, + pub patch_file: PathBuf, +- pub sys_file: PathBuf, ++ pub status_file: PathBuf, ++ pub module: kernel::ModuleInfo, ++ pub functions: HashMap>, // object name -> function list + pub checksum: String, + } ++ ++impl KernelPatch { ++ fn parse_functions(patch_file: &Path) -> Result>> { ++ const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; ++ const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; ++ ++ let mmap = fs::mmap(patch_file).map_err(|e| { ++ anyhow!( ++ "Failed to mmap '{}', {}", ++ patch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ let file = File::parse(mmap.as_ref()).map_err(|e| { ++ anyhow!( ++ "Failed to parse '{}', {}", ++ patch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ // Read sections ++ let function_section = file ++ .section_by_name(KPATCH_FUNCS_SECTION) ++ .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; ++ let string_section = file ++ .section_by_name(KPATCH_STRINGS_SECTION) ++ .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; ++ let function_data = function_section.data().map_err(|e| { ++ anyhow!( ++ "Failed to read section '{}', {}", ++ KPATCH_FUNCS_SECTION, ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ let string_data = string_section.data().map_err(|e| { ++ anyhow!( ++ "Failed to read section '{}', {}", ++ KPATCH_STRINGS_SECTION, ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ // Resolve patch functions ++ let (slice, _) = object::slice_from_bytes::( ++ function_data, ++ function_data.len() / ffi::KPATCH_FUNCTION_SIZE, ++ ) ++ .map_err(|_| anyhow!("Invalid patch function layout"))?; ++ ++ let mut functions: Vec<_> = slice ++ .iter() ++ .map(|function| KernelPatchFunction { ++ name: OsString::new(), ++ object: OsString::new(), ++ old_addr: function.old_addr, ++ old_size: function.old_size, ++ new_addr: function.new_addr, ++ new_size: function.new_size, ++ }) ++ .collect(); ++ ++ // Relocate patch functions ++ for relocation in ffi::KpatchRelocationIterator(function_section.relocations()) { ++ let (name_offset, name_reloc) = relocation.name; ++ let (object_offset, obj_reloc) = relocation.object; ++ ++ // Relocate patch function name ++ let name_index = ++ (name_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; ++ let name_function = functions ++ .get_mut(name_index) ++ .with_context(|| format!("Invalid patch function index, index={}", name_index))?; ++ let name_addend = name_reloc.addend() as usize; ++ name_function.name = CStr::from_bytes_with_next_nul(&string_data[name_addend..]) ++ .map_err(|_| anyhow!("Invalid patch function name"))? ++ .to_os_string(); ++ ++ // Relocate patch function object ++ let object_index = ++ (object_offset as usize - ffi::KPATCH_OBJECT_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; ++ let object_function = functions ++ .get_mut(object_index) ++ .with_context(|| format!("Invalid patch object index, index={}", object_index))?; ++ let object_addend = obj_reloc.addend() as usize; ++ object_function.object = CStr::from_bytes_with_next_nul(&string_data[object_addend..]) ++ .map_err(|_| anyhow!("Invalid patch object name"))? ++ .to_os_string(); ++ } ++ ++ // group functions by it's object ++ let mut function_map: HashMap<_, Vec<_>> = HashMap::new(); ++ for function in functions { ++ function_map ++ .entry(function.object.clone()) ++ .or_default() ++ .push(function); ++ } ++ ++ Ok(function_map) ++ } ++ ++ pub fn parse( ++ name: S, ++ patch_info: Arc, ++ patch_entity: &PatchEntity, ++ patch_file: P, ++ ) -> Result ++ where ++ S: AsRef, ++ P: AsRef, ++ { ++ const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; ++ const KPATCH_STATUS_FILE_NAME: &str = "enabled"; ++ ++ let patch_file = patch_file.as_ref(); ++ let module = kernel::module_info(patch_file).map_err(|e| { ++ anyhow!( ++ "Failed to parse '{}' modinfo, {}", ++ patch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ let patch = Self { ++ uuid: patch_entity.uuid, ++ name: name.as_ref().to_os_string(), ++ info: patch_info.clone(), ++ pkg_name: patch_info.target.full_name(), ++ target_name: patch_entity.patch_name.as_os_str().to_os_string(), ++ patch_file: patch_file.to_path_buf(), ++ status_file: PathBuf::from(KPATCH_SYS_DIR) ++ .join(&module.name) ++ .join(KPATCH_STATUS_FILE_NAME), ++ module, ++ functions: Self::parse_functions(patch_file)?, ++ checksum: patch_entity.checksum.clone(), ++ }; ++ Ok(patch) ++ } ++} ++ ++impl std::fmt::Display for KernelPatch { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{}", self.name.to_string_lossy()) ++ } ++} +diff --git a/syscared/src/patch/entity/patch.rs b/syscared/src/patch/entity/patch.rs +index 2ff398a..0941389 100644 +--- a/syscared/src/patch/entity/patch.rs ++++ b/syscared/src/patch/entity/patch.rs +@@ -78,6 +78,9 @@ impl std::cmp::Ord for Patch { + + impl std::fmt::Display for Patch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- write!(f, "{}", self.name().to_string_lossy()) ++ match self { ++ Patch::KernelPatch(patch) => write!(f, "{}", patch), ++ Patch::UserPatch(patch) => write!(f, "{}", patch), ++ } + } + } +diff --git a/syscared/src/patch/entity/symbol.rs b/syscared/src/patch/entity/symbol.rs +deleted file mode 100644 +index c7845aa..0000000 +--- a/syscared/src/patch/entity/symbol.rs ++++ /dev/null +@@ -1,54 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscared is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::ffi::OsString; +- +-/// Patch function definiation +-#[derive(Clone)] +-pub struct PatchFunction { +- pub name: OsString, +- pub target: OsString, +- pub old_addr: u64, +- pub old_size: u64, +- pub new_addr: u64, +- pub new_size: u64, +-} +- +-impl std::fmt::Debug for PatchFunction { +- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- f.debug_struct("PatchFunction") +- .field("name", &self.name) +- .field("target", &self.target) +- .field("old_addr", &format!("0x{}", self.old_addr)) +- .field("old_size", &format!("0x{}", self.old_size)) +- .field("new_addr", &format!("0x{}", self.new_addr)) +- .field("new_size", &format!("0x{}", self.new_size)) +- .finish() +- } +-} +- +-impl std::fmt::Display for PatchFunction { +- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- write!( +- f, +- "name: {}, target: {}, old_addr: 0x{:x}, old_size: 0x{:x}, new_addr: 0x{:x}, new_size: 0x{:x}", +- self.name.to_string_lossy(), +- self.target.to_string_lossy(), +- self.old_addr, +- self.old_size, +- self.new_addr, +- self.new_size, +- ) +- } +-} +diff --git a/syscared/src/patch/entity/upatch.rs b/syscared/src/patch/entity/upatch.rs +index 0fbacb4..aaaee1c 100644 +--- a/syscared/src/patch/entity/upatch.rs ++++ b/syscared/src/patch/entity/upatch.rs +@@ -12,11 +12,66 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{ffi::OsString, path::PathBuf, sync::Arc}; ++use std::{ ++ ffi::{CStr, OsStr, OsString}, ++ path::{Path, PathBuf}, ++ sync::Arc, ++}; + +-use syscare_abi::PatchInfo; ++use anyhow::{anyhow, Context, Result}; ++use object::{File, Object, ObjectSection}; + use uuid::Uuid; + ++use syscare_abi::{PatchEntity, PatchInfo}; ++use syscare_common::{ffi::CStrExt, fs}; ++ ++mod ffi { ++ use std::os::raw::{c_char, c_ulong}; ++ ++ use object::{Pod, Relocation, SectionRelocationIterator}; ++ ++ #[repr(C)] ++ #[derive(Debug, Clone, Copy)] ++ /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` ++ pub struct UpatchFunction { ++ pub new_addr: c_ulong, ++ pub new_size: c_ulong, ++ pub old_addr: c_ulong, ++ pub old_size: c_ulong, ++ pub sympos: c_ulong, ++ pub name: *const c_char, ++ } ++ ++ /* ++ * SAFETY: This struct is ++ * - #[repr(C)] ++ * - have no invalid byte values ++ * - have no padding ++ */ ++ unsafe impl Pod for UpatchFunction {} ++ ++ pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); ++ pub const UPATCH_FUNCTION_OFFSET: usize = 40; ++ ++ pub struct UpatchRelocation { ++ pub _addr: (u64, Relocation), ++ pub name: (u64, Relocation), ++ } ++ ++ pub struct UpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); ++ ++ impl Iterator for UpatchRelocationIterator<'_, '_> { ++ type Item = UpatchRelocation; ++ ++ fn next(&mut self) -> Option { ++ if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { ++ return Some(UpatchRelocation { _addr: addr, name }); ++ } ++ None ++ } ++ } ++} ++ + /// User patch function definition + #[derive(Clone)] + pub struct UserPatchFunction { +@@ -31,10 +86,10 @@ impl std::fmt::Debug for UserPatchFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("UserPatchFunction") + .field("name", &self.name) +- .field("old_addr", &format!("0x{}", self.old_addr)) +- .field("old_size", &format!("0x{}", self.old_size)) +- .field("new_addr", &format!("0x{}", self.new_addr)) +- .field("new_size", &format!("0x{}", self.new_size)) ++ .field("old_addr", &format!("0x{:x}", self.old_addr)) ++ .field("old_size", &format!("0x{:x}", self.old_size)) ++ .field("new_addr", &format!("0x{:x}", self.new_addr)) ++ .field("new_size", &format!("0x{:x}", self.new_size)) + .finish() + } + } +@@ -60,8 +115,118 @@ pub struct UserPatch { + pub name: OsString, + pub info: Arc, + pub pkg_name: String, +- pub functions: Vec, +- pub patch_file: PathBuf, + pub target_elf: PathBuf, ++ pub patch_file: PathBuf, ++ pub functions: Vec, + pub checksum: String, + } ++ ++impl UserPatch { ++ fn parse_functions(patch_file: &Path) -> Result> { ++ const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; ++ const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; ++ ++ let mmap = fs::mmap(patch_file).map_err(|e| { ++ anyhow!( ++ "Failed to mmap '{}', {}", ++ patch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ let file = File::parse(mmap.as_ref()).map_err(|e| { ++ anyhow!( ++ "Failed to parse '{}', {}", ++ patch_file.display(), ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ // Read sections ++ let function_section = file ++ .section_by_name(UPATCH_FUNCS_SECTION) ++ .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; ++ let string_section = file ++ .section_by_name(UPATCH_STRINGS_SECTION) ++ .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; ++ let function_data = function_section.data().map_err(|e| { ++ anyhow!( ++ "Failed to read section '{}', {}", ++ UPATCH_FUNCS_SECTION, ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ let string_data = string_section.data().map_err(|e| { ++ anyhow!( ++ "Failed to read section '{}', {}", ++ UPATCH_STRINGS_SECTION, ++ e.to_string().to_lowercase() ++ ) ++ })?; ++ ++ // Resolve patch functions ++ let (slice, _) = object::slice_from_bytes::( ++ function_data, ++ function_data.len() / ffi::UPATCH_FUNCTION_SIZE, ++ ) ++ .map_err(|_| anyhow!("Invalid patch function layout"))?; ++ ++ let mut functions: Vec<_> = slice ++ .iter() ++ .map(|function| UserPatchFunction { ++ name: OsString::new(), ++ old_addr: function.old_addr, ++ old_size: function.old_size, ++ new_addr: function.new_addr, ++ new_size: function.new_size, ++ }) ++ .collect(); ++ ++ // Relocate patch functions ++ for relocation in ffi::UpatchRelocationIterator(function_section.relocations()) { ++ let (value, reloc) = relocation.name; ++ ++ let index = (value as usize - ffi::UPATCH_FUNCTION_OFFSET) / ffi::UPATCH_FUNCTION_SIZE; ++ let function = functions ++ .get_mut(index) ++ .with_context(|| format!("Invalid patch function index, index={}", index))?; ++ let addend = reloc.addend() as usize; ++ ++ function.name = CStr::from_bytes_with_next_nul(&string_data[addend..]) ++ .map_err(|_| anyhow!("Invalid patch function name"))? ++ .to_os_string(); ++ } ++ ++ Ok(functions) ++ } ++ ++ pub fn parse( ++ name: S, ++ patch_info: Arc, ++ patch_entity: &PatchEntity, ++ patch_file: P, ++ ) -> Result ++ where ++ S: AsRef, ++ P: AsRef, ++ { ++ let patch_file = patch_file.as_ref(); ++ ++ let patch = Self { ++ uuid: patch_entity.uuid, ++ name: name.as_ref().to_os_string(), ++ info: patch_info.clone(), ++ pkg_name: patch_info.target.full_name(), ++ target_elf: patch_entity.patch_target.clone(), ++ patch_file: patch_file.to_path_buf(), ++ functions: Self::parse_functions(patch_file)?, ++ checksum: patch_entity.checksum.clone(), ++ }; ++ Ok(patch) ++ } ++} ++ ++impl std::fmt::Display for UserPatch { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{}", self.name.to_string_lossy()) ++ } ++} +diff --git a/syscared/src/patch/manager.rs b/syscared/src/patch/manager.rs +index 4bf65ac..7248430 100644 +--- a/syscared/src/patch/manager.rs ++++ b/syscared/src/patch/manager.rs +@@ -24,10 +24,16 @@ use lazy_static::lazy_static; + use log::{debug, error, info, trace, warn}; + use uuid::Uuid; + +-use syscare_abi::PatchStatus; ++use syscare_abi::{PatchEntity, PatchInfo, PatchStatus, PatchType, PATCH_INFO_MAGIC}; + use syscare_common::{concat_os, ffi::OsStrExt, fs, util::serde}; + +-use crate::{config::PatchConfig, patch::resolver::PatchResolver}; ++use crate::{ ++ config::PatchConfig, ++ patch::{ ++ entity::{KernelPatch, UserPatch}, ++ PATCH_INFO_FILE_NAME, ++ }, ++}; + + use super::{ + driver::{PatchDriver, PatchOpFlag}, +@@ -40,7 +46,7 @@ type TransitionAction = + &'static (dyn Fn(&mut PatchManager, &Patch, PatchOpFlag) -> Result<()> + Sync); + + const PATCH_CHECK: TransitionAction = &PatchManager::driver_check_patch; +-const PATCH_APPLY: TransitionAction = &PatchManager::driver_apply_patch; ++const PATCH_LOAD: TransitionAction = &PatchManager::driver_load_patch; + const PATCH_REMOVE: TransitionAction = &PatchManager::driver_remove_patch; + const PATCH_ACTIVE: TransitionAction = &PatchManager::driver_active_patch; + const PATCH_DEACTIVE: TransitionAction = &PatchManager::driver_deactive_patch; +@@ -49,9 +55,9 @@ const PATCH_DECLINE: TransitionAction = &PatchManager::driver_decline_patch; + + lazy_static! { + static ref STATUS_TRANSITION_MAP: IndexMap> = indexmap! { +- (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_APPLY], +- (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE], +- (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE, PATCH_ACCEPT], ++ (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_LOAD], ++ (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE], ++ (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE, PATCH_ACCEPT], + (PatchStatus::Deactived, PatchStatus::NotApplied) => vec![PATCH_REMOVE], + (PatchStatus::Deactived, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_ACTIVE], + (PatchStatus::Deactived, PatchStatus::Accepted) => vec![PATCH_ACTIVE, PATCH_ACCEPT], +@@ -133,7 +139,7 @@ impl PatchManager { + .unwrap_or_default(); + + if status == PatchStatus::Unknown { +- status = self.driver_patch_status(patch, PatchOpFlag::Normal)?; ++ status = self.driver_get_patch_status(patch)?; + self.set_patch_status(patch, status)?; + } + +@@ -141,6 +147,7 @@ impl PatchManager { + } + + pub fn check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { ++ info!("Check patch '{}'", patch); + self.driver.check_patch(patch, flag)?; + self.driver.check_confliction(patch, flag)?; + +@@ -245,20 +252,20 @@ impl PatchManager { + + debug!("Reading patch status..."); + let status_map: IndexMap = +- serde::deserialize(&self.patch_status_file).context("Failed to read patch status")?; ++ serde::deserialize(&self.patch_status_file) ++ .context("Failed to read patch status file")?; + for (uuid, status) in &status_map { + debug!("Patch '{}' status: {}", uuid, status); + } +- +- let restore_list = status_map +- .into_iter() +- .filter(|(_, status)| !accepted_only || (*status == PatchStatus::Accepted)); +- for (uuid, status) in restore_list { ++ for (uuid, status) in status_map { ++ if accepted_only && (status != PatchStatus::Accepted) { ++ continue; ++ } + match self.find_patch_by_uuid(&uuid) { + Ok(patch) => { +- debug!("Restore patch '{}' status to '{}'", patch, status); ++ info!("Restore patch '{}' status to '{}'", patch, status); + if let Err(e) = self.do_status_transition(&patch, status, PatchOpFlag::Force) { +- error!("{}", e); ++ error!("{:?}", e); + } + } + Err(e) => { +@@ -277,7 +284,7 @@ impl PatchManager { + let status_keys = self.status_map.keys().copied().collect::>(); + for patch_uuid in status_keys { + if !self.patch_map.contains_key(&patch_uuid) { +- trace!("Patch '{}' was removed, remove its status", patch_uuid); ++ trace!("Patch '{}' was removed", patch_uuid); + self.status_map.remove(&patch_uuid); + } + } +@@ -329,19 +336,110 @@ impl PatchManager { + } + + impl PatchManager { ++ fn parse_user_patch( ++ root_dir: &Path, ++ patch_info: Arc, ++ patch_entity: &PatchEntity, ++ ) -> Result { ++ let patch_name = concat_os!( ++ patch_info.target.short_name(), ++ "/", ++ patch_info.name(), ++ "/", ++ patch_entity.patch_target.file_name().unwrap_or_default() ++ ); ++ let patch_file = root_dir.join(&patch_entity.patch_name); ++ ++ let patch = UserPatch::parse(&patch_name, patch_info, patch_entity, patch_file) ++ .with_context(|| { ++ format!( ++ "Failed to parse patch '{}' ({})", ++ patch_entity.uuid, ++ patch_name.to_string_lossy(), ++ ) ++ })?; ++ ++ debug!("Found patch '{}' ({})", patch.uuid, patch); ++ Ok(patch) ++ } ++ ++ fn parse_kernel_patch( ++ root_dir: &Path, ++ patch_info: Arc, ++ patch_entity: &PatchEntity, ++ ) -> Result { ++ const KPATCH_EXTENSION: &str = "ko"; ++ ++ let patch_name = concat_os!( ++ patch_info.target.short_name(), ++ "/", ++ patch_info.name(), ++ "/", ++ &patch_entity.patch_target, ++ ); ++ let mut patch_file = root_dir.join(&patch_entity.patch_name); ++ patch_file.set_extension(KPATCH_EXTENSION); ++ ++ let patch = KernelPatch::parse(&patch_name, patch_info, patch_entity, patch_file) ++ .with_context(|| { ++ format!( ++ "Failed to parse patch '{}' ({})", ++ patch_entity.uuid, ++ patch_name.to_string_lossy(), ++ ) ++ })?; ++ ++ debug!("Found patch '{}' ({})", patch.uuid, patch); ++ Ok(patch) ++ } ++ ++ fn parse_patches(root_dir: &Path) -> Result> { ++ let root_name = root_dir.file_name().expect("Invalid patch root directory"); ++ let patch_metadata = root_dir.join(PATCH_INFO_FILE_NAME); ++ let patch_info = Arc::new( ++ serde::deserialize_with_magic::(patch_metadata, PATCH_INFO_MAGIC) ++ .with_context(|| { ++ format!( ++ "Failed to parse patch '{}' metadata", ++ root_name.to_string_lossy(), ++ ) ++ })?, ++ ); ++ ++ patch_info ++ .entities ++ .iter() ++ .map(|patch_entity| { ++ let patch_info = patch_info.clone(); ++ match patch_info.kind { ++ PatchType::UserPatch => Ok(Patch::UserPatch(Self::parse_user_patch( ++ root_dir, ++ patch_info, ++ patch_entity, ++ )?)), ++ PatchType::KernelPatch => Ok(Patch::KernelPatch(Self::parse_kernel_patch( ++ root_dir, ++ patch_info, ++ patch_entity, ++ )?)), ++ } ++ }) ++ .collect::>>() ++ } ++ + fn scan_patches>(directory: P) -> Result>> { + const TRAVERSE_OPTION: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; + + let mut patch_map = IndexMap::new(); + +- info!("Scanning patches from {}...", directory.as_ref().display()); +- for patch_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { +- let resolve_result = PatchResolver::resolve_patch(&patch_dir) +- .with_context(|| format!("Failed to resolve patch from {}", patch_dir.display())); +- match resolve_result { ++ info!( ++ "Scanning patches from '{}'...", ++ directory.as_ref().display() ++ ); ++ for root_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { ++ match Self::parse_patches(&root_dir) { + Ok(patches) => { + for patch in patches { +- debug!("Detected patch '{}'", patch); + patch_map.insert(*patch.uuid(), Arc::new(patch)); + } + } +@@ -399,15 +497,12 @@ impl PatchManager { + bail!("Cannot set patch '{}' status to '{}'", patch, value); + } + +- let (index, _) = self.status_map.insert_full(*patch.uuid(), value); +- if let Some(last_index) = self +- .status_map +- .last() +- .and_then(|(key, _)| self.status_map.get_index_of(key)) +- { +- if index != last_index { +- self.status_map.move_index(index, last_index); +- } ++ let uuid = *patch.uuid(); ++ let (curr_index, _) = self.status_map.insert_full(uuid, value); ++ ++ let last_index = self.status_map.len().saturating_sub(1); ++ if curr_index != last_index { ++ self.status_map.move_index(curr_index, last_index); + } + + Ok(()) +@@ -415,16 +510,16 @@ impl PatchManager { + } + + impl PatchManager { +- fn driver_patch_status(&self, patch: &Patch, _flag: PatchOpFlag) -> Result { +- self.driver.patch_status(patch) +- } +- + fn driver_check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + self.driver.check_patch(patch, flag) + } + +- fn driver_apply_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { +- self.driver.apply_patch(patch)?; ++ fn driver_get_patch_status(&self, patch: &Patch) -> Result { ++ self.driver.get_patch_status(patch) ++ } ++ ++ fn driver_load_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { ++ self.driver.load_patch(patch)?; + self.set_patch_status(patch, PatchStatus::Deactived) + } + +diff --git a/syscared/src/patch/mod.rs b/syscared/src/patch/mod.rs +index 26a97da..edd247a 100644 +--- a/syscared/src/patch/mod.rs ++++ b/syscared/src/patch/mod.rs +@@ -16,7 +16,6 @@ pub mod driver; + pub mod entity; + pub mod manager; + pub mod monitor; +-pub mod resolver; + pub mod transaction; + + const PATCH_INFO_FILE_NAME: &str = "patch_info"; +diff --git a/syscared/src/patch/resolver/kpatch.rs b/syscared/src/patch/resolver/kpatch.rs +deleted file mode 100644 +index f8885ff..0000000 +--- a/syscared/src/patch/resolver/kpatch.rs ++++ /dev/null +@@ -1,226 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscared is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ +- ffi::{CStr, OsString}, +- path::{Path, PathBuf}, +- sync::Arc, +-}; +- +-use anyhow::{anyhow, Context, Result}; +-use object::{NativeFile, Object, ObjectSection}; +- +-use syscare_abi::{PatchEntity, PatchInfo}; +-use syscare_common::{ +- concat_os, +- ffi::{CStrExt, OsStrExt}, +- fs, +-}; +- +-use super::PatchResolverImpl; +-use crate::patch::entity::{KernelPatch, KernelPatchFunction, Patch}; +- +-const KPATCH_SUFFIX: &str = ".ko"; +-const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; +-const KPATCH_SYS_FILE_NAME: &str = "enabled"; +- +-mod ffi { +- use std::os::raw::{c_char, c_long, c_ulong}; +- +- use object::{ +- read::elf::{ElfSectionRelocationIterator, FileHeader}, +- Pod, Relocation, +- }; +- +- #[repr(C)] +- #[derive(Debug, Clone, Copy)] +- /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` +- pub struct KpatchFunction { +- pub new_addr: c_ulong, +- pub new_size: c_ulong, +- pub old_addr: c_ulong, +- pub old_size: c_ulong, +- pub sympos: u64, +- pub name: *const c_char, +- pub obj_name: *const c_char, +- pub ref_name: *const c_char, +- pub ref_offset: c_long, +- } +- +- pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); +- pub const KPATCH_FUNCTION_OFFSET: usize = 40; +- pub const KPATCH_OBJECT_OFFSET: usize = 48; +- +- /* +- * SAFETY: This struct is +- * - #[repr(C)] +- * - have no invalid byte values +- * - have no padding +- */ +- unsafe impl Pod for KpatchFunction {} +- +- pub struct KpatchRelocation { +- pub _addr: (u64, Relocation), +- pub name: (u64, Relocation), +- pub object: (u64, Relocation), +- } +- +- pub struct KpatchRelocationIterator<'data, 'file, Elf: FileHeader>( +- ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, +- ); +- +- impl<'data, 'file, Elf: FileHeader> KpatchRelocationIterator<'data, 'file, Elf> { +- pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { +- Self(relocations) +- } +- } +- +- impl Iterator for KpatchRelocationIterator<'_, '_, Elf> { +- type Item = KpatchRelocation; +- +- fn next(&mut self) -> Option { +- if let (Some(addr), Some(name), Some(object)) = +- (self.0.next(), self.0.next(), self.0.next()) +- { +- return Some(KpatchRelocation { +- _addr: addr, +- name, +- object, +- }); +- } +- None +- } +- } +-} +- +-const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; +-const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; +- +-pub struct KpatchResolverImpl; +- +-impl KpatchResolverImpl { +- #[inline] +- fn resolve_patch_file(patch: &mut KernelPatch) -> Result<()> { +- let patch_file = fs::mmap(&patch.patch_file) +- .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; +- let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; +- +- // Read sections +- let function_section = patch_elf +- .section_by_name(KPATCH_FUNCS_SECTION) +- .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; +- let string_section = patch_elf +- .section_by_name(KPATCH_STRINGS_SECTION) +- .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; +- let function_data = function_section +- .data() +- .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; +- let string_data = string_section +- .data() +- .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; +- +- // Resolve patch functions +- let patch_functions = &mut patch.functions; +- let kpatch_function_slice = object::slice_from_bytes::( +- function_data, +- function_data.len() / ffi::KPATCH_FUNCTION_SIZE, +- ) +- .map(|(f, _)| f) +- .map_err(|_| anyhow!("Invalid data format")) +- .context("Failed to resolve patch functions")?; +- +- for function in kpatch_function_slice { +- patch_functions.push(KernelPatchFunction { +- name: OsString::new(), +- object: OsString::new(), +- old_addr: function.old_addr, +- old_size: function.old_size, +- new_addr: function.new_addr, +- new_size: function.new_size, +- }); +- } +- +- // Relocate patch functions +- for relocation in ffi::KpatchRelocationIterator::new(function_section.relocations()) { +- let (name_reloc_offset, name_reloc) = relocation.name; +- let (object_reloc_offset, obj_reloc) = relocation.object; +- +- // Relocate patch function name +- let name_index = (name_reloc_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) +- / ffi::KPATCH_FUNCTION_SIZE; +- let name_function = patch_functions +- .get_mut(name_index) +- .context("Failed to find patch function")?; +- let name_offset = name_reloc.addend() as usize; +- let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) +- .context("Failed to parse patch object name")? +- .to_os_string(); +- +- name_function.name = name_string; +- +- // Relocate patch function object +- let object_index = (object_reloc_offset as usize - ffi::KPATCH_OBJECT_OFFSET) +- / ffi::KPATCH_FUNCTION_SIZE; +- let object_function = patch_functions +- .get_mut(object_index) +- .context("Failed to find patch function")?; +- let object_offset = obj_reloc.addend() as usize; +- let object_string = CStr::from_bytes_with_next_nul(&string_data[object_offset..]) +- .context("Failed to parse patch function name")? +- .to_os_string(); +- +- object_function.object = object_string; +- } +- +- Ok(()) +- } +-} +- +-impl PatchResolverImpl for KpatchResolverImpl { +- fn resolve_patch( +- &self, +- patch_root: &Path, +- patch_info: Arc, +- patch_entity: &PatchEntity, +- ) -> Result { +- let target_name = patch_entity.patch_name.as_os_str().to_os_string(); +- let module_name = patch_entity.patch_name.replace(['-', '.'], "_"); +- let patch_file = patch_root.join(concat_os!(&patch_entity.patch_name, KPATCH_SUFFIX)); +- let sys_file = PathBuf::from(KPATCH_SYS_DIR) +- .join(&module_name) +- .join(KPATCH_SYS_FILE_NAME); +- +- let mut patch = KernelPatch { +- uuid: patch_entity.uuid, +- name: concat_os!( +- patch_info.target.short_name(), +- "/", +- patch_info.name(), +- "/", +- &patch_entity.patch_target +- ), +- info: patch_info.clone(), +- pkg_name: patch_info.target.full_name(), +- target_name, +- module_name, +- patch_file, +- sys_file, +- functions: Vec::new(), +- checksum: patch_entity.checksum.clone(), +- }; +- Self::resolve_patch_file(&mut patch).context("Failed to resolve patch")?; +- +- Ok(Patch::KernelPatch(patch)) +- } +-} +diff --git a/syscared/src/patch/resolver/mod.rs b/syscared/src/patch/resolver/mod.rs +deleted file mode 100644 +index 7c4a504..0000000 +--- a/syscared/src/patch/resolver/mod.rs ++++ /dev/null +@@ -1,63 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscared is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{path::Path, sync::Arc}; +- +-use super::{entity::Patch, PATCH_INFO_FILE_NAME}; +- +-use anyhow::{Context, Result}; +-use syscare_abi::{PatchEntity, PatchInfo, PatchType, PATCH_INFO_MAGIC}; +-use syscare_common::util::serde; +- +-mod kpatch; +-mod upatch; +- +-use kpatch::KpatchResolverImpl; +-use upatch::UpatchResolverImpl; +- +-pub trait PatchResolverImpl { +- fn resolve_patch( +- &self, +- patch_root: &Path, +- patch_info: Arc, +- patch_entity: &PatchEntity, +- ) -> Result; +-} +- +-pub struct PatchResolver; +- +-impl PatchResolver { +- pub fn resolve_patch>(directory: P) -> Result> { +- let patch_root = directory.as_ref(); +- let patch_info = Arc::new( +- serde::deserialize_with_magic::( +- patch_root.join(PATCH_INFO_FILE_NAME), +- PATCH_INFO_MAGIC, +- ) +- .context("Failed to resolve patch metadata")?, +- ); +- let resolver: &dyn PatchResolverImpl = match patch_info.kind { +- PatchType::UserPatch => &UpatchResolverImpl, +- PatchType::KernelPatch => &KpatchResolverImpl, +- }; +- +- let mut patch_list = Vec::with_capacity(patch_info.entities.len()); +- for patch_entity in &patch_info.entities { +- let patch = resolver.resolve_patch(patch_root, patch_info.clone(), patch_entity)?; +- patch_list.push(patch); +- } +- +- Ok(patch_list) +- } +-} +diff --git a/syscared/src/patch/resolver/upatch.rs b/syscared/src/patch/resolver/upatch.rs +deleted file mode 100644 +index a5e4ad0..0000000 +--- a/syscared/src/patch/resolver/upatch.rs ++++ /dev/null +@@ -1,182 +0,0 @@ +-// SPDX-License-Identifier: Mulan PSL v2 +-/* +- * Copyright (c) 2024 Huawei Technologies Co., Ltd. +- * syscared is licensed under Mulan PSL v2. +- * You can use this software according to the terms and conditions of the Mulan PSL v2. +- * You may obtain a copy of Mulan PSL v2 at: +- * http://license.coscl.org.cn/MulanPSL2 +- * +- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +- * See the Mulan PSL v2 for more details. +- */ +- +-use std::{ +- ffi::{CStr, OsString}, +- path::Path, +- sync::Arc, +-}; +- +-use anyhow::{anyhow, Context, Result}; +-use object::{NativeFile, Object, ObjectSection}; +- +-use syscare_abi::{PatchEntity, PatchInfo}; +-use syscare_common::{concat_os, ffi::CStrExt, fs}; +- +-use super::PatchResolverImpl; +-use crate::patch::entity::{Patch, UserPatch, UserPatchFunction}; +- +-mod ffi { +- use std::os::raw::{c_char, c_ulong}; +- +- use object::{ +- read::elf::{ElfSectionRelocationIterator, FileHeader}, +- Pod, Relocation, +- }; +- +- #[repr(C)] +- #[derive(Debug, Clone, Copy)] +- /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` +- pub struct UpatchFunction { +- pub new_addr: c_ulong, +- pub new_size: c_ulong, +- pub old_addr: c_ulong, +- pub old_size: c_ulong, +- pub sympos: c_ulong, +- pub name: *const c_char, +- } +- +- /* +- * SAFETY: This struct is +- * - #[repr(C)] +- * - have no invalid byte values +- * - have no padding +- */ +- unsafe impl Pod for UpatchFunction {} +- +- pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); +- pub const UPATCH_FUNCTION_OFFSET: usize = 40; +- +- pub struct UpatchRelocation { +- pub _addr: (u64, Relocation), +- pub name: (u64, Relocation), +- } +- +- pub struct UpatchRelocationIterator<'data, 'file, Elf: FileHeader>( +- ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, +- ); +- +- impl<'data, 'file, Elf: FileHeader> UpatchRelocationIterator<'data, 'file, Elf> { +- pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { +- Self(relocations) +- } +- } +- +- impl Iterator for UpatchRelocationIterator<'_, '_, Elf> { +- type Item = UpatchRelocation; +- +- fn next(&mut self) -> Option { +- if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { +- return Some(UpatchRelocation { _addr: addr, name }); +- } +- None +- } +- } +-} +- +-const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; +-const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; +- +-pub struct UpatchResolverImpl; +- +-impl UpatchResolverImpl { +- #[inline] +- fn resolve_patch_elf(patch: &mut UserPatch) -> Result<()> { +- let patch_file = fs::mmap(&patch.patch_file) +- .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; +- let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; +- +- // Read sections +- let function_section = patch_elf +- .section_by_name(UPATCH_FUNCS_SECTION) +- .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; +- let string_section = patch_elf +- .section_by_name(UPATCH_STRINGS_SECTION) +- .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; +- let function_data = function_section +- .data() +- .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; +- let string_data = string_section +- .data() +- .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; +- +- // Resolve patch functions +- let patch_functions = &mut patch.functions; +- let upatch_function_slice = object::slice_from_bytes::( +- function_data, +- function_data.len() / ffi::UPATCH_FUNCTION_SIZE, +- ) +- .map(|(f, _)| f) +- .map_err(|_| anyhow!("Invalid data format")) +- .context("Failed to resolve patch functions")?; +- +- for function in upatch_function_slice { +- patch_functions.push(UserPatchFunction { +- name: OsString::new(), +- old_addr: function.old_addr, +- old_size: function.old_size, +- new_addr: function.new_addr, +- new_size: function.new_size, +- }); +- } +- +- // Relocate patch functions +- for relocation in ffi::UpatchRelocationIterator::new(function_section.relocations()) { +- let (name_reloc_offset, name_reloc) = relocation.name; +- +- let name_index = (name_reloc_offset as usize - ffi::UPATCH_FUNCTION_OFFSET) +- / ffi::UPATCH_FUNCTION_SIZE; +- let name_function = patch_functions +- .get_mut(name_index) +- .context("Failed to find patch function")?; +- let name_offset = name_reloc.addend() as usize; +- let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) +- .context("Failed to parse patch function name")? +- .to_os_string(); +- +- name_function.name = name_string; +- } +- +- Ok(()) +- } +-} +- +-impl PatchResolverImpl for UpatchResolverImpl { +- fn resolve_patch( +- &self, +- patch_root: &Path, +- patch_info: Arc, +- patch_entity: &PatchEntity, +- ) -> Result { +- let mut patch = UserPatch { +- uuid: patch_entity.uuid, +- name: concat_os!( +- patch_info.target.short_name(), +- "/", +- patch_info.name(), +- "/", +- fs::file_name(&patch_entity.patch_target) +- ), +- info: patch_info.clone(), +- pkg_name: patch_info.target.full_name(), +- patch_file: patch_root.join(&patch_entity.patch_name), +- target_elf: patch_entity.patch_target.clone(), +- functions: Vec::new(), +- checksum: patch_entity.checksum.clone(), +- }; +- Self::resolve_patch_elf(&mut patch).context("Failed to resolve patch")?; +- +- Ok(Patch::UserPatch(patch)) +- } +-} +-- +2.43.0 + diff --git a/0137-project-update-Cargo.lock.patch b/0137-project-update-Cargo.lock.patch new file mode 100644 index 0000000000000000000000000000000000000000..62bd328e28ad6b3138fc7ad20570ae7709fe5363 --- /dev/null +++ b/0137-project-update-Cargo.lock.patch @@ -0,0 +1,30 @@ +From c62926a1d600225434829f6dd8a308be80a9d357 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Wed, 28 May 2025 18:29:34 +0800 +Subject: [PATCH] project: update Cargo.lock + +Signed-off-by: renoseven +--- + Cargo.lock | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/Cargo.lock b/Cargo.lock +index b1598cd..2014167 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1171,10 +1171,11 @@ name = "syscare-common" + version = "1.2.2" + dependencies = [ + "anyhow", +- "lazy_static", + "log", + "memmap2", + "nix", ++ "num_cpus", ++ "object", + "regex", + "serde", + "serde_cbor", +-- +2.43.0 + diff --git a/0138-project-update-CMakeLists.txt.patch b/0138-project-update-CMakeLists.txt.patch new file mode 100644 index 0000000000000000000000000000000000000000..18879f9b6b4794f479cc79c0e40d8005c167c599 --- /dev/null +++ b/0138-project-update-CMakeLists.txt.patch @@ -0,0 +1,166 @@ +From 46340c6b6c14c9d7b582c30659bd37fc16e04f2f Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Wed, 11 Jun 2025 13:31:46 +0800 +Subject: [PATCH] project: update CMakeLists.txt + +Signed-off-by: renoseven +--- + CMakeLists.txt | 80 ++++++++++++++++++++++++-------------------------- + 1 file changed, 39 insertions(+), 41 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index e1a9051..e923258 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -37,29 +37,29 @@ if(GIT_FOUND) + ERROR_QUIET + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) +- set(PROJECT_BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") ++ set(BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") + else() +- set(PROJECT_BUILD_VERSION "${BUILD_VERSION}") ++ set(BUILD_VERSION "${BUILD_VERSION}") + endif() + + # Build configurations + if(ENABLE_ASAN) +- set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-asan") +- list(APPEND PROJECT_C_BUILD_FLAGS -fsanitize=address -fno-omit-frame-pointer) +- list(APPEND PROJECT_C_LIBRARIES asan) ++ set(BUILD_VERSION "${BUILD_VERSION}-asan") ++ list(APPEND BUILD_FLAGS_C -fsanitize=address -fno-omit-frame-pointer) ++ list(APPEND LINK_LIBRARIES_C asan) + endif() + + if(ENABLE_GCOV) +- set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-gcov") +- list(APPEND PROJECT_C_BUILD_FLAGS -ftest-coverage -fprofile-arcs) +- list(APPEND PROJECT_RUST_FLAGS -C instrument-coverage) +- list(APPEND PROJECT_C_LIBRARIES gcov) ++ set(BUILD_VERSION "${BUILD_VERSION}-gcov") ++ list(APPEND BUILD_FLAGS_C -ftest-coverage -fprofile-arcs) ++ list(APPEND BUILD_FLAGS_RUST -C instrument-coverage) ++ list(APPEND LINK_LIBRARIES_C gcov) + endif() + + # Build flags +-list(APPEND PROJECT_C_BUILD_FLAGS +- -std=gnu99 -Wall -O2 -Werror -Wextra +- -DBUILD_VERSION="${PROJECT_BUILD_VERSION}" -D_FORTIFY_SOURCE=2 ++list(APPEND BUILD_FLAGS_C ++ -std=gnu99 -O2 -Wall -Wextra -Werror ++ -DBUILD_VERSION="${BUILD_VERSION}" -D_FORTIFY_SOURCE=2 + -Wtrampolines -Wformat=2 -Wstrict-prototypes -Wdate-time + -Wstack-usage=8192 -Wfloat-equal -Wswitch-default + -Wshadow -Wconversion -Wcast-qual -Wunused -Wundef +@@ -70,16 +70,16 @@ list(APPEND PROJECT_C_BUILD_FLAGS + + # The -Werror=cast-align compiler flag causes issues on riscv64 GCC, + # while the same operations do not error on aarch64. This appears to be +-# a compiler-specific problem. Temporarily disable this option as a ++# a compiler-specific problem. Temporarily disable this option as a + # workaround since applying fixes would require intrusive code changes + # across multiple files. + if(NOT ARCH STREQUAL "riscv64") +- list(APPEND PROJECT_C_BUILD_FLAGS ++ list(APPEND BUILD_FLAGS_C + -Wcast-align + ) + endif() + +-list(APPEND PROJECT_RUST_FLAGS ++list(APPEND BUILD_FLAGS_RUST + --cfg unsound_local_offset + -D warnings + -C link-arg=-s +@@ -91,7 +91,7 @@ list(APPEND PROJECT_RUST_FLAGS + ) + + # Link flags +-list(APPEND PROJECT_C_LINK_FLAGS ++list(APPEND LINK_FLAGS_C + -pie + -Wl,-z,relro,-z,now + -Wl,-z,noexecstack -rdynamic +@@ -100,9 +100,9 @@ list(APPEND PROJECT_C_LINK_FLAGS + ) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") +- list(APPEND PROJECT_C_BUILD_FLAGS -g) ++ list(APPEND BUILD_FLAGS_C -g) + elseif(CMAKE_BUILD_TYPE STREQUAL "Release") +- list(APPEND PROJECT_C_LINK_FLAGS -s) ++ list(APPEND LINK_FLAGS_C -s) + endif() + + # Install directories +@@ -120,44 +120,42 @@ message("███████║ ██║ ███████║╚█ + message("╚══════╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝") + message("---------------------------------------------------------") + message("-- Build type: ${CMAKE_BUILD_TYPE}") +-message("-- Build version: ${PROJECT_BUILD_VERSION}") +-message("-- Rust flags: ${PROJECT_RUST_FLAGS}") +-message("-- Build flags: ${PROJECT_C_BUILD_FLAGS}") +-message("-- Link flags: ${PROJECT_C_LINK_FLAGS}") +-message("-- Link libraries: ${PROJECT_C_LIBRARIES}") ++message("-- Build version: ${BUILD_VERSION}") ++message("-- Rust flags: ${BUILD_FLAGS_RUST}") ++message("-- Build flags: ${BUILD_FLAGS_C}") ++message("-- Link flags: ${LINK_FLAGS_C}") ++message("-- Link libraries: ${LINK_LIBRARIES_C}") + message("-- Binary directory: ${SYSCARE_BINARY_DIR}") + message("-- Libexec directory: ${SYSCARE_LIBEXEC_DIR}") + message("-- Service directory: ${SYSCARE_SERVICE_DIR}") + message("---------------------------------------------------------") + + # Apply all flags +-add_compile_options(${PROJECT_C_BUILD_FLAGS}) +-add_link_options(${PROJECT_C_LINK_FLAGS}) +-link_libraries(${PROJECT_C_LIBRARIES}) ++add_compile_options(${BUILD_FLAGS_C}) ++add_link_options(${LINK_FLAGS_C}) ++link_libraries(${LINK_LIBRARIES_C}) + + # Build rust executables +-set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust") ++set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/target") + set(RUST_OUTPUT_DIR "${RUST_TARGET_DIR}/release") +-foreach(FLAG IN LISTS PROJECT_RUST_FLAGS) +- set(RUST_FLAGS "${RUST_FLAGS} ${FLAG}") ++foreach(CURR_FLAG IN LISTS BUILD_FLAGS_RUST) ++ set(RUST_FLAGS "${RUST_FLAGS} ${CURR_FLAG}") + endforeach() + +-add_custom_target(rust-build ALL +- COMMENT "Building rust executables..." +- COMMAND ${CMAKE_COMMAND} -E env +- "BUILD_VERSION=${PROJECT_BUILD_VERSION}" +- "RUSTFLAGS=${RUST_FLAGS}" +- cargo build --release --target-dir "${RUST_TARGET_DIR}" +- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ++add_custom_target(syscare ALL ++ COMMAND cargo build --release --target-dir ${RUST_TARGET_DIR} ++ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + +-# Generate upatch helpers +-add_custom_target(generate-upatch-helpers ALL +- COMMENT "Generating upatch helpers..." ++set_target_properties(syscare PROPERTIES ++ ENVIRONMENT "RUSTFLAGS=${RUST_FLAGS};BUILD_VERSION=${BUILD_VERSION}" ++ ADDITIONAL_CLEAN_FILES "${RUST_TARGET_DIR}" ++) ++ ++add_custom_target(upatch-helpers ALL + COMMAND ln -sf upatch-helper upatch-cc + COMMAND ln -sf upatch-helper upatch-c++ +- DEPENDS +- rust-build ++ DEPENDS syscare + WORKING_DIRECTORY ${RUST_OUTPUT_DIR} + ) + +-- +2.43.0 + diff --git a/0139-upatch-diff-fix-special-section-cannot-be-correlated.patch b/0139-upatch-diff-fix-special-section-cannot-be-correlated.patch new file mode 100644 index 0000000000000000000000000000000000000000..650894409d608465d20ee966e3f95454f8f95f21 --- /dev/null +++ b/0139-upatch-diff-fix-special-section-cannot-be-correlated.patch @@ -0,0 +1,34 @@ +From caf86ca270ef90eb6d80bfb581fe38abde7ad11a Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Tue, 24 Jun 2025 17:32:59 +0800 +Subject: [PATCH] upatch-diff: fix special section cannot be correlated issue + + In some special case, section name may be changed like from +'.text.xxxx' to '.text.xxxx.unlikely', while the section content +does not change. This issue caused section correlation failure. +We found that the section corresponding symbol was same. Thus, +we linked symbol section when correlating symbols. + +Signed-off-by: renoseven +--- + upatch-diff/elf-correlate.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/upatch-diff/elf-correlate.c b/upatch-diff/elf-correlate.c +index 8e2f765..5991c2c 100644 +--- a/upatch-diff/elf-correlate.c ++++ b/upatch-diff/elf-correlate.c +@@ -38,6 +38,10 @@ static void correlate_symbol(struct symbol *sym_orig, + sym_patched->name, sym_orig->name); + sym_patched->name = sym_orig->name; + } ++ if (sym_patched->sec && sym_orig->sec) { ++ sym_patched->sec->twin = sym_orig->sec; ++ sym_orig->sec->twin = sym_patched->sec; ++ } + } + + void upatch_correlate_symbols(struct upatch_elf *uelf_source, +-- +2.43.0 + diff --git a/0140-upatch-helper-fix-compilation-command-detection.patch b/0140-upatch-helper-fix-compilation-command-detection.patch new file mode 100644 index 0000000000000000000000000000000000000000..a3ed4c7df82d66483ac854aceed3a3a587ee5415 --- /dev/null +++ b/0140-upatch-helper-fix-compilation-command-detection.patch @@ -0,0 +1,103 @@ +From cce6eb45c2de98034eb442ceaa851cdde279873c Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 26 Jun 2025 15:31:30 +0800 +Subject: [PATCH] upatch-helper: fix compilation command detection + +Fix the logic for detecting compilation commands to properly handle: +* Non-compilation flags (--help, --version, -E, etc.) +* Special source files (-, @args.txt) +* Source file extensions (.c, .cpp, .s, etc.) +* Compile signal flags (-x) + +This ensures the helper only intercepts actual compilation commands. + +Signed-off-by: renoseven +--- + upatch-helper/src/main.rs | 57 +++++++++++++++++++++++++++++++++++++-- + 1 file changed, 55 insertions(+), 2 deletions(-) + +diff --git a/upatch-helper/src/main.rs b/upatch-helper/src/main.rs +index 3992367..7c7b2b8 100644 +--- a/upatch-helper/src/main.rs ++++ b/upatch-helper/src/main.rs +@@ -25,6 +25,20 @@ use uuid::Uuid; + const COMPILER_KEYWORDS_CC: &[&str] = &["cc", "clang"]; + const COMPILER_KEYWORDS_CXX: &[&str] = &["++", "xx"]; + ++const COMPILER_EXCLUDE_FLAGS: &[&str] = &[ ++ "-E", // Preprocess only; does not compile. ++ "--version", // Print compiler version and exit. ++ "--help", // Print help message and exit. ++ "--target-help", // Print target-specific help and exit. ++ "-dumpversion", // Print compiler version string (e.g., "11.2.0") and exit. ++ "-dumpmachine", // Print compiler target machine (e.g., "x86_64-linux-gnu") and exit. ++ "-###", // Dry run: print commands that would be executed, but do not run them. ++]; ++const COMPILER_EXCLUDE_FLAG_PREFIXES: &[&str] = &["--print-"]; ++const COMPILER_COMPILE_SIGNAL_FLAGS: &[&str] = &["-x"]; ++const COMPILER_SPECIAL_SOURCE_FILES: &[&str] = &["-", "@args.txt"]; ++const COMPILER_SOURCE_FILE_EXTENSIONS: &[&str] = &["c", "cc", "cpp", "cxx", "s", "S"]; ++ + const HELPER_ENV_NAME_CC: &str = "UPATCH_HELPER_CC"; + const HELPER_ENV_NAME_CXX: &str = "UPATCH_HELPER_CXX"; + const HELPER_ENV_NAMES: &[(&[&str], &str)] = &[ +@@ -32,7 +46,6 @@ const HELPER_ENV_NAMES: &[(&[&str], &str)] = &[ + (COMPILER_KEYWORDS_CXX, HELPER_ENV_NAME_CXX), + ]; + +-const COMPILE_FLAG_NAME: &str = "-c"; + const COMPILE_OPTIONS_GNU: &[&str] = &[ + "-gdwarf", // generate dwarf debuginfo + "-ffunction-sections", // generate corresponding section for each function +@@ -55,7 +68,47 @@ const UPATCH_ID_PREFIX: &str = ".upatch_"; + + #[inline(always)] + fn is_compilation(args: &[OsString]) -> bool { +- args.iter().any(|arg| arg == COMPILE_FLAG_NAME) ++ /* check exclude flags */ ++ for arg in args.iter().skip(1) { ++ if COMPILER_EXCLUDE_FLAGS ++ .iter() ++ .any(|&flag| arg == OsStr::new(flag)) ++ { ++ return false; ++ } ++ if COMPILER_EXCLUDE_FLAG_PREFIXES ++ .iter() ++ .any(|&prefix| arg.as_bytes().starts_with(prefix.as_bytes())) ++ { ++ return false; ++ } ++ } ++ ++ /* check compile flag & source file */ ++ for arg in args.iter().skip(1) { ++ if COMPILER_COMPILE_SIGNAL_FLAGS ++ .iter() ++ .any(|&name| arg == OsStr::new(name)) ++ { ++ return true; ++ } ++ if COMPILER_SPECIAL_SOURCE_FILES ++ .iter() ++ .any(|&name| arg == OsStr::new(name)) ++ { ++ return true; ++ } ++ if let Some(src_ext) = Path::new(arg).extension() { ++ if COMPILER_SOURCE_FILE_EXTENSIONS ++ .iter() ++ .any(|&ext| src_ext == OsStr::new(ext)) ++ { ++ return true; ++ } ++ } ++ } ++ ++ false + } + + #[inline(always)] +-- +2.43.0 + diff --git a/0141-upatch-build-use-last-write-wins-strategy-to-solving.patch b/0141-upatch-build-use-last-write-wins-strategy-to-solving.patch new file mode 100644 index 0000000000000000000000000000000000000000..5bc290a94c0244e05d20db3255818ab8f24c82d8 --- /dev/null +++ b/0141-upatch-build-use-last-write-wins-strategy-to-solving.patch @@ -0,0 +1,59 @@ +From 66e9fa966685c4f37144dc91c2fc276691413a2c Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 26 Jun 2025 17:39:54 +0800 +Subject: [PATCH] upatch-build: use last-write-wins strategy to solving upatch + id conflicts + +Replace the conservative filtering approach with an explicit overwrite policy: +1. Process objects in descending order of ID count (complex objects first) +2. Later objects automatically overwrite existing upatch_id mappings +3. Print detailed conflict warnings + +Signed-off-by: renoseven +--- + upatch-build/src/file_relation.rs | 25 +++++++++++++------------ + 1 file changed, 13 insertions(+), 12 deletions(-) + +diff --git a/upatch-build/src/file_relation.rs b/upatch-build/src/file_relation.rs +index 3c71e50..677cb8b 100644 +--- a/upatch-build/src/file_relation.rs ++++ b/upatch-build/src/file_relation.rs +@@ -291,22 +291,23 @@ impl FileRelation { + ); + + // We want subsequent objects to contain more identifiers. +- object_info.sort_by(|(_, _, lhs), (_, _, rhs)| lhs.len().cmp(&rhs.len())); ++ object_info.sort_by(|(_, _, lhs), (_, _, rhs)| rhs.len().cmp(&lhs.len())); + + let mut upatch_id_map = IndexMap::new(); +- while let Some((object_file, archive_file, mut upatch_ids)) = object_info.pop() { +- // Remove identifiers in other objects +- for (_, _, ids) in &object_info { +- upatch_ids.retain(|id| !ids.contains(id)); +- } +- // Current object is fully composed of other objects, we skip it. +- if upatch_ids.is_empty() { +- warn!("Skipped {}", object_file.display()); +- } +- for upatch_id in upatch_ids { +- upatch_id_map.insert(upatch_id, (object_file.clone(), archive_file.clone())); ++ for (object, archive, ids) in object_info { ++ for id in ids { ++ let result = upatch_id_map.insert(id.clone(), (object.clone(), archive.clone())); ++ if let Some((old_object, _)) = result { ++ warn!( ++ "{}: Object {} is replaced by {}", ++ id.to_string_lossy(), ++ old_object.display(), ++ object.display() ++ ); ++ } + } + } ++ + ensure!( + !upatch_id_map.is_empty(), + "Cannot find any upatch id in {}", +-- +2.43.0 + diff --git a/0142-syscare-build-check-source-package-version-release-m.patch b/0142-syscare-build-check-source-package-version-release-m.patch new file mode 100644 index 0000000000000000000000000000000000000000..367d96bd3b88a9ff8399a12392266e02dc131a80 --- /dev/null +++ b/0142-syscare-build-check-source-package-version-release-m.patch @@ -0,0 +1,64 @@ +From 6f11929a3a4d5b1918296b128b51184590dad16a Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Fri, 27 Jun 2025 11:52:07 +0800 +Subject: [PATCH] syscare-build: check source package version & release matched + to debuginfo package + +Ensure each debug package's version and release matches at least one source +package to prevent symbol mismatches. + +Signed-off-by: renoseven +--- + syscare-build/src/main.rs | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/syscare-build/src/main.rs b/syscare-build/src/main.rs +index 27754af..7d4d201 100644 +--- a/syscare-build/src/main.rs ++++ b/syscare-build/src/main.rs +@@ -122,7 +122,8 @@ impl SyscareBuild { + } + + fn collect_package_info(&self) -> Result> { +- let mut pkg_list = Vec::new(); ++ let mut source_pkg_list = Vec::new(); ++ let mut debug_pkg_list = Vec::new(); + + for pkg_path in self.args.source.clone() { + let mut pkg_info = PKG_IMPL.parse_package_info(&pkg_path)?; +@@ -139,7 +140,7 @@ impl SyscareBuild { + bail!("File {} is not a source package", pkg_info.short_name()); + } + +- pkg_list.push(pkg_info); ++ source_pkg_list.push(pkg_info); + } + + for pkg_path in self.args.debuginfo.clone() { +@@ -160,10 +161,22 @@ impl SyscareBuild { + self.args.patch_arch + ); + } ++ debug_pkg_list.push(pkg_info); + } + info!("------------------------------"); + +- Ok(pkg_list) ++ for source_pkg in &source_pkg_list { ++ ensure!( ++ debug_pkg_list.iter().any(|debug_pkg| { ++ source_pkg.version == debug_pkg.version ++ && source_pkg.release == debug_pkg.release ++ }), ++ "Package {} has no matching debuginfo package", ++ source_pkg.full_name(), ++ ); ++ } ++ ++ Ok(source_pkg_list) + } + + fn prepare_source_code( +-- +2.43.0 + diff --git a/0143-syscare-build-match-source-package-to-debuginfo-pack.patch b/0143-syscare-build-match-source-package-to-debuginfo-pack.patch new file mode 100644 index 0000000000000000000000000000000000000000..7b0b3ed7aefa45572798e4ca4e5de5534778b510 --- /dev/null +++ b/0143-syscare-build-match-source-package-to-debuginfo-pack.patch @@ -0,0 +1,41 @@ +From 993b4b629b7698d91c9bd17685e7e433a65e0d6c Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 3 Jul 2025 20:10:59 +0800 +Subject: [PATCH] syscare-build: match source package to debuginfo package + +In some case, source packages would be more than debuginfo packages. +This caused a package matching error. So we changed to use debuginfo as the key. + +Signed-off-by: renoseven +--- + syscare-build/src/main.rs | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/syscare-build/src/main.rs b/syscare-build/src/main.rs +index 7d4d201..8df6e02 100644 +--- a/syscare-build/src/main.rs ++++ b/syscare-build/src/main.rs +@@ -165,14 +165,14 @@ impl SyscareBuild { + } + info!("------------------------------"); + +- for source_pkg in &source_pkg_list { ++ for debug_pkg in &debug_pkg_list { + ensure!( +- debug_pkg_list.iter().any(|debug_pkg| { +- source_pkg.version == debug_pkg.version +- && source_pkg.release == debug_pkg.release ++ source_pkg_list.iter().any(|source_pkg| { ++ debug_pkg.version == source_pkg.version ++ && debug_pkg.release == source_pkg.release + }), +- "Package {} has no matching debuginfo package", +- source_pkg.full_name(), ++ "Package {} has no matching source package", ++ debug_pkg.full_name(), + ); + } + +-- +2.43.0 + diff --git a/0144-syscare-build-fix-patch-file-list-disorder-when-buil.patch b/0144-syscare-build-fix-patch-file-list-disorder-when-buil.patch new file mode 100644 index 0000000000000000000000000000000000000000..d4d9ea299851fa4e42dce7a4ca9315d5d2f60b01 --- /dev/null +++ b/0144-syscare-build-fix-patch-file-list-disorder-when-buil.patch @@ -0,0 +1,103 @@ +From f03f87fc88db7743af8533c5c0a51843539e5fd3 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Thu, 3 Jul 2025 21:43:48 +0800 +Subject: [PATCH] syscare-build: fix patch file list disorder when building + cumulative patch + +1. stop sorting user-input patch list when collecting patch files +2. rewrite all patch file paths in metadata with metadata directory +3. use patch file list instead of looking up patches from metadata directory + +Signed-off-by: renoseven +--- + syscare-build/src/main.rs | 15 ++++++++------- + syscare-build/src/patch/metadata.rs | 12 ++++++++++-- + syscare-build/src/patch/mod.rs | 2 -- + syscare-build/src/patch/patch_helper.rs | 2 +- + 4 files changed, 19 insertions(+), 12 deletions(-) + +diff --git a/syscare-build/src/main.rs b/syscare-build/src/main.rs +index 8df6e02..df38a17 100644 +--- a/syscare-build/src/main.rs ++++ b/syscare-build/src/main.rs +@@ -37,7 +37,7 @@ use package::{ + PackageBuildRoot, PackageBuilderFactory, PackageFormat, PackageImpl, PackageSpecBuilderFactory, + PackageSpecWriterFactory, + }; +-use patch::{PatchBuilderFactory, PatchHelper, PatchMetadata, PATCH_FILE_EXT}; ++use patch::{PatchBuilderFactory, PatchHelper, PatchMetadata}; + + const CLI_NAME: &str = "syscare build"; + const CLI_VERSION: &str = env!("CARGO_PKG_VERSION"); +@@ -292,12 +292,13 @@ impl SyscareBuild { + } + + // Override patch list +- let mut new_patch_files = PatchHelper::collect_patch_files(fs::list_files_by_ext( +- &patch_metadata.metadata_dir, +- PATCH_FILE_EXT, +- fs::TraverseOptions { recursive: false }, +- )?) +- .context("Failed to collect patch file from metadata directory")?; ++ let mut new_patch_files = PatchHelper::collect_patch_files( ++ saved_patch_info ++ .patches ++ .iter() ++ .map(|patch_file| &patch_file.path), ++ ) ++ .context("Failed to collect patch file from patch metadata")?; + + new_patch_files.extend(patch_files); + patch_files = new_patch_files; +diff --git a/syscare-build/src/patch/metadata.rs b/syscare-build/src/patch/metadata.rs +index 0911693..5eadf52 100644 +--- a/syscare-build/src/patch/metadata.rs ++++ b/syscare-build/src/patch/metadata.rs +@@ -90,8 +90,16 @@ impl PatchMetadata { + .decompress(&self.root_dir) + .context("Failed to decompress patch metadata")?; + +- serde::deserialize_with_magic::(&self.metadata_path, PATCH_INFO_MAGIC) +- .context("Failed to read patch metadata") ++ let mut patch_info: PatchInfo = ++ serde::deserialize_with_magic(&self.metadata_path, PATCH_INFO_MAGIC) ++ .context("Failed to read patch metadata")?; ++ ++ // rewrite file path to metadata directory path ++ for patch_file in &mut patch_info.patches { ++ patch_file.path = self.metadata_dir.join(&patch_file.name) ++ } ++ ++ Ok(patch_info) + } + + pub fn write>( +diff --git a/syscare-build/src/patch/mod.rs b/syscare-build/src/patch/mod.rs +index f590b1c..558bfea 100644 +--- a/syscare-build/src/patch/mod.rs ++++ b/syscare-build/src/patch/mod.rs +@@ -12,8 +12,6 @@ + * See the Mulan PSL v2 for more details. + */ + +-pub const PATCH_FILE_EXT: &str = "patch"; +- + mod kernel_patch; + mod metadata; + mod patch_builder; +diff --git a/syscare-build/src/patch/patch_helper.rs b/syscare-build/src/patch/patch_helper.rs +index fc8324a..9fc3ed1 100644 +--- a/syscare-build/src/patch/patch_helper.rs ++++ b/syscare-build/src/patch/patch_helper.rs +@@ -47,7 +47,7 @@ impl PatchHelper { + digest: file_digest, + }); + } +- patch_list.sort(); ++ + Ok(patch_list) + } + } +-- +2.43.0 + diff --git a/0145-project-fix-version-does-not-affect-on-rust-executab.patch b/0145-project-fix-version-does-not-affect-on-rust-executab.patch new file mode 100644 index 0000000000000000000000000000000000000000..fee949340634841657db6bda1593e0f2ae24aea3 --- /dev/null +++ b/0145-project-fix-version-does-not-affect-on-rust-executab.patch @@ -0,0 +1,34 @@ +From 59553198ac8592d2a018bda27d275cbbd61263a2 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Fri, 11 Jul 2025 17:39:15 +0800 +Subject: [PATCH] project: fix version does not affect on rust executable + +Signed-off-by: renoseven +--- + CMakeLists.txt | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index e923258..3ad9eca 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -143,12 +143,14 @@ foreach(CURR_FLAG IN LISTS BUILD_FLAGS_RUST) + endforeach() + + add_custom_target(syscare ALL +- COMMAND cargo build --release --target-dir ${RUST_TARGET_DIR} ++ COMMAND ${CMAKE_COMMAND} -E env ++ "BUILD_VERSION=${BUILD_VERSION}" ++ "RUSTFLAGS=${RUST_FLAGS}" ++ cargo build --release --target-dir "${RUST_TARGET_DIR}" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + + set_target_properties(syscare PROPERTIES +- ENVIRONMENT "RUSTFLAGS=${RUST_FLAGS};BUILD_VERSION=${BUILD_VERSION}" + ADDITIONAL_CLEAN_FILES "${RUST_TARGET_DIR}" + ) + +-- +2.43.0 + diff --git a/0146-syscared-fix-active-deactive-process-list-calculatio.patch b/0146-syscared-fix-active-deactive-process-list-calculatio.patch new file mode 100644 index 0000000000000000000000000000000000000000..ebfb6d8e7649a14a3ff52d1b1185ab57fc3b3c32 --- /dev/null +++ b/0146-syscared-fix-active-deactive-process-list-calculatio.patch @@ -0,0 +1,197 @@ +From 081c4bf26d53c4cbfed2777c7fa1137d2fac4c24 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Fri, 25 Jul 2025 20:11:02 +0800 +Subject: [PATCH] syscared: fix active & deactive process list calculation + error + +Signed-off-by: renoseven +--- + syscared/src/patch/driver/upatch/mod.rs | 39 +++++++++------ + syscared/src/patch/driver/upatch/target.rs | 57 +++++++++++++++------- + 2 files changed, 64 insertions(+), 32 deletions(-) + +diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs +index cc00f53..58f01df 100644 +--- a/syscared/src/patch/driver/upatch/mod.rs ++++ b/syscared/src/patch/driver/upatch/mod.rs +@@ -23,7 +23,7 @@ use std::{ + }; + + use anyhow::{bail, ensure, Result}; +-use log::{debug, warn}; ++use log::{debug, info, warn}; + use parking_lot::RwLock; + use uuid::Uuid; + +@@ -207,12 +207,11 @@ impl UserPatchDriver { + patch_target.clean_dead_process(&process_list); + + let all_patches = patch_target.all_patches().collect::>(); +- let need_actived = patch_target.need_actived(&process_list); +- + for (uuid, patch_file) in all_patches { ++ let need_actived = patch_target.need_actived_process(&process_list, &uuid); + if !need_actived.is_empty() { +- debug!( +- "Upatch: Activating patch '{}' ({}) for process {:?}", ++ info!( ++ "Upatch: Activating patch '{}' ({}) on new process {:?}", + uuid, + target_elf.display(), + need_actived, +@@ -220,10 +219,10 @@ impl UserPatchDriver { + } + for &pid in &need_actived { + match sys::active_patch(&uuid, pid, target_elf, &patch_file) { +- Ok(_) => patch_target.add_process(pid), ++ Ok(_) => patch_target.process_register_patch(pid, &uuid), + Err(e) => { + warn!( +- "Upatch: Failed to active patch '{}' for process {}, {}", ++ "Upatch: Failed to active patch '{}' on process {}, {}", + uuid, + pid, + e.to_string().to_lowercase(), +@@ -266,7 +265,13 @@ impl UserPatchDriver { + let start_watch = !patch_target.is_patched(); + + // Active patch +- let need_actived = patch_target.need_actived(&process_list); ++ let need_actived = patch_target.need_actived_process(&process_list, &patch.uuid); ++ info!( ++ "Upatch: Activating patch '{}' ({}) on process {:?}", ++ patch.uuid, ++ patch.target_elf.display(), ++ need_actived, ++ ); + + let mut results = Vec::new(); + for pid in need_actived { +@@ -290,10 +295,10 @@ impl UserPatchDriver { + // Process results + for (pid, result) in results { + match result { +- Ok(_) => patch_target.add_process(pid), ++ Ok(_) => patch_target.process_register_patch(pid, &patch.uuid), + Err(e) => { + warn!( +- "Upatch: Failed to active patch '{}' for process {}, {}", ++ "Upatch: Failed to active patch '{}' on process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), +@@ -322,10 +327,16 @@ impl UserPatchDriver { + patch_target.clean_dead_process(&process_list); + + // Deactive patch +- let need_deactive = patch_target.need_deactived(&process_list); ++ let need_deactived = patch_target.need_deactived_process(&process_list, &patch.uuid); ++ info!( ++ "Upatch: Deactivating patch '{}' ({}) on process {:?}", ++ patch.uuid, ++ patch.target_elf.display(), ++ need_deactived, ++ ); + + let mut results = Vec::new(); +- for pid in need_deactive { ++ for pid in need_deactived { + let result = + sys::deactive_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); + results.push((pid, result)); +@@ -347,10 +358,10 @@ impl UserPatchDriver { + // Process results + for (pid, result) in results { + match result { +- Ok(_) => patch_target.remove_process(pid), ++ Ok(_) => patch_target.process_unregister_patch(pid, &patch.uuid), + Err(e) => { + warn!( +- "Upatch: Failed to deactive patch '{}' for process {}, {}", ++ "Upatch: Failed to deactive patch '{}' on process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), +diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs +index e0e9f88..a2a5f81 100644 +--- a/syscared/src/patch/driver/upatch/target.rs ++++ b/syscared/src/patch/driver/upatch/target.rs +@@ -24,36 +24,57 @@ use crate::patch::entity::UserPatch; + + #[derive(Debug, Default)] + pub struct PatchTarget { +- process_list: HashSet, +- patch_map: HashMap, // uuid -> patch file ++ process_map: HashMap>, // pid -> patch list ++ patch_map: HashMap, // uuid -> patch file + collision_map: HashMap>, // function old addr -> patch collision list + } + + impl PatchTarget { +- pub fn add_process(&mut self, pid: i32) { +- self.process_list.insert(pid); ++ pub fn process_register_patch(&mut self, pid: i32, uuid: &Uuid) { ++ self.process_map.entry(pid).or_default().insert(*uuid); + } + +- pub fn remove_process(&mut self, pid: i32) { +- self.process_list.remove(&pid); ++ pub fn process_unregister_patch(&mut self, pid: i32, uuid: &Uuid) { ++ if let Some(patch_list) = self.process_map.get_mut(&pid) { ++ patch_list.remove(uuid); ++ } + } + +- pub fn clean_dead_process(&mut self, process_list: &HashSet) { +- self.process_list.retain(|pid| process_list.contains(pid)); ++ pub fn need_actived_process(&self, process_list: &HashSet, uuid: &Uuid) -> HashSet { ++ let mut need_actived = HashSet::with_capacity(process_list.len()); ++ ++ for pid in process_list { ++ match self.process_map.get(pid) { ++ Some(patch_list) => { ++ if !patch_list.contains(uuid) { ++ need_actived.insert(*pid); ++ } ++ } ++ None => { ++ need_actived.insert(*pid); ++ } ++ } ++ } ++ ++ need_actived + } + +- pub fn need_actived(&self, process_list: &HashSet) -> HashSet { +- process_list +- .difference(&self.process_list) +- .copied() +- .collect() ++ pub fn need_deactived_process(&self, process_list: &HashSet, uuid: &Uuid) -> HashSet { ++ let mut need_deactived = HashSet::with_capacity(process_list.len()); ++ ++ for pid in process_list { ++ if let Some(patch_list) = self.process_map.get(pid) { ++ if patch_list.contains(uuid) { ++ need_deactived.insert(*pid); ++ } ++ } ++ } ++ ++ need_deactived + } + +- pub fn need_deactived(&self, process_list: &HashSet) -> HashSet { +- process_list +- .intersection(&self.process_list) +- .copied() +- .collect() ++ pub fn clean_dead_process(&mut self, process_list: &HashSet) { ++ self.process_map.retain(|pid, _| process_list.contains(pid)); + } + } + +-- +2.43.0 + diff --git a/0147-syscared-fix-new-process-patch-active-disorder-issue.patch b/0147-syscared-fix-new-process-patch-active-disorder-issue.patch new file mode 100644 index 0000000000000000000000000000000000000000..dc5229327f3a0756c59ca6a9a0fdbcc1faa23ca4 --- /dev/null +++ b/0147-syscared-fix-new-process-patch-active-disorder-issue.patch @@ -0,0 +1,36 @@ +From 9b11391de94603690f2d197a2993785748d32752 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Tue, 29 Jul 2025 15:55:33 +0800 +Subject: [PATCH] syscared: fix new process patch active disorder issue + +Signed-off-by: renoseven +--- + syscared/src/patch/driver/upatch/target.rs | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs +index a2a5f81..be5aabc 100644 +--- a/syscared/src/patch/driver/upatch/target.rs ++++ b/syscared/src/patch/driver/upatch/target.rs +@@ -17,15 +17,15 @@ use std::{ + path::PathBuf, + }; + +-use indexmap::IndexSet; ++use indexmap::{IndexMap, IndexSet}; + use uuid::Uuid; + + use crate::patch::entity::UserPatch; + + #[derive(Debug, Default)] + pub struct PatchTarget { +- process_map: HashMap>, // pid -> patch list +- patch_map: HashMap, // uuid -> patch file ++ process_map: HashMap>, // pid -> patch list ++ patch_map: IndexMap, // uuid -> patch file + collision_map: HashMap>, // function old addr -> patch collision list + } + +-- +2.43.0 + diff --git a/0148-syscare-return-error-if-external-command-is-an-inval.patch b/0148-syscare-return-error-if-external-command-is-an-inval.patch new file mode 100644 index 0000000000000000000000000000000000000000..bd59dd9e9f5d3c95af525c3ec8922c2723b4b38f --- /dev/null +++ b/0148-syscare-return-error-if-external-command-is-an-inval.patch @@ -0,0 +1,66 @@ +From 01582050c03ab434323554e190d99ff769b9e713 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Fri, 1 Aug 2025 13:18:53 +0800 +Subject: [PATCH] syscare: return error if external command is an invalid + executable + +Signed-off-by: renoseven +--- + syscare/src/main.rs | 30 +++++++++++++++--------------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/syscare/src/main.rs b/syscare/src/main.rs +index 38416f2..ff7a4ec 100644 +--- a/syscare/src/main.rs ++++ b/syscare/src/main.rs +@@ -12,9 +12,9 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{env, ffi::OsString, os::unix::process::CommandExt, process::Command}; ++use std::{env, ffi::OsString, process::Command}; + +-use anyhow::{bail, Context, Result}; ++use anyhow::{anyhow, Context, Result}; + use args::SubCommand; + use flexi_logger::{LogSpecification, Logger, WriteMode}; + use log::{debug, LevelFilter}; +@@ -41,22 +41,22 @@ const EXTERNAL_CMD_PREFIX: &str = "syscare-"; + fn exec_external_cmd(mut args: Vec) -> Result<()> { + let program = concat_os!(EXTERNAL_CMD_PREFIX, args.remove(0).trim()); + +- let error = Command::new(&program).args(&args).exec(); +- match error.kind() { +- std::io::ErrorKind::NotFound => { +- bail!( ++ let _ = Command::new(&program) ++ .args(&args) ++ .status() ++ .map_err(|e| match e.kind() { ++ std::io::ErrorKind::NotFound => anyhow!( + "External command '{}' is not installed", + program.to_string_lossy() +- ); +- } +- _ => { +- bail!( +- "Failed to execute '{}', {}", ++ ), ++ _ => anyhow!( ++ "Failed to execute '{}': {}", + program.to_string_lossy(), +- error +- ); +- } +- } ++ e.to_string().to_lowercase() ++ ), ++ })?; ++ ++ Ok(()) + } + + fn main() -> Result<()> { +-- +2.43.0 + diff --git a/0149-syscare-use-os-error-code-for-external-command-failu.patch b/0149-syscare-use-os-error-code-for-external-command-failu.patch new file mode 100644 index 0000000000000000000000000000000000000000..70e3432035ea9f617cb755ddd181b9b080d13665 --- /dev/null +++ b/0149-syscare-use-os-error-code-for-external-command-failu.patch @@ -0,0 +1,79 @@ +From a8a8b0b4f7d50cc55bae8ed3441f3ffae6ef4ccb Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Fri, 8 Aug 2025 15:59:23 +0800 +Subject: [PATCH] syscare: use os error code for external command failures + +Modify exec_external_cmd() to use the underlying OS error code when external +command execution fails, falling back to 1 if no specific error code is +available. + +The change preserves the single exit point while improving error handling +granularity by using std::io::Error::raw_os_error() for failed command executions. + +Signed-off-by: renoseven +--- + syscare/src/main.rs | 30 ++++++++++-------------------- + 1 file changed, 10 insertions(+), 20 deletions(-) + +diff --git a/syscare/src/main.rs b/syscare/src/main.rs +index ff7a4ec..7fde0ca 100644 +--- a/syscare/src/main.rs ++++ b/syscare/src/main.rs +@@ -12,9 +12,9 @@ + * See the Mulan PSL v2 for more details. + */ + +-use std::{env, ffi::OsString, process::Command}; ++use std::{env, ffi::OsString, process}; + +-use anyhow::{anyhow, Context, Result}; ++use anyhow::{Context, Result}; + use args::SubCommand; + use flexi_logger::{LogSpecification, Logger, WriteMode}; + use log::{debug, LevelFilter}; +@@ -38,25 +38,16 @@ const PATH_ENV_NAME: &str = "PATH"; + const PATH_ENV_VALUE: &str = "/usr/libexec/syscare"; + const EXTERNAL_CMD_PREFIX: &str = "syscare-"; + +-fn exec_external_cmd(mut args: Vec) -> Result<()> { ++fn exec_external_cmd(mut args: Vec) -> ! { + let program = concat_os!(EXTERNAL_CMD_PREFIX, args.remove(0).trim()); + +- let _ = Command::new(&program) +- .args(&args) +- .status() +- .map_err(|e| match e.kind() { +- std::io::ErrorKind::NotFound => anyhow!( +- "External command '{}' is not installed", +- program.to_string_lossy() +- ), +- _ => anyhow!( +- "Failed to execute '{}': {}", +- program.to_string_lossy(), +- e.to_string().to_lowercase() +- ), +- })?; ++ let exit_status = process::Command::new(&program).args(&args).status(); ++ let exit_code = match exit_status { ++ Ok(status) => status.code().unwrap_or(1), ++ Err(e) => e.raw_os_error().unwrap_or(1), ++ }; + +- Ok(()) ++ process::exit(exit_code); + } + + fn main() -> Result<()> { +@@ -85,8 +76,7 @@ fn main() -> Result<()> { + + debug!("Start with {:#?}", args); + if let SubCommand::External(cmd_args) = args.subcommand { +- self::exec_external_cmd(cmd_args)?; +- return Ok(()); ++ self::exec_external_cmd(cmd_args); + } + + debug!("Initializing rpc client..."); +-- +2.43.0 + diff --git a/0150-syscare-add-error-log-when-external-command-exec-fai.patch b/0150-syscare-add-error-log-when-external-command-exec-fai.patch new file mode 100644 index 0000000000000000000000000000000000000000..3b5893def4e3f65134998e653f7f5bdf06671934 --- /dev/null +++ b/0150-syscare-add-error-log-when-external-command-exec-fai.patch @@ -0,0 +1,48 @@ +From 39ed31559684760faa1e8fa8e54f42cd7649574d Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Mon, 11 Aug 2025 16:45:24 +0800 +Subject: [PATCH] syscare: add error log when external command exec failure + +Signed-off-by: renoseven +--- + syscare/src/main.rs | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/syscare/src/main.rs b/syscare/src/main.rs +index 7fde0ca..c636199 100644 +--- a/syscare/src/main.rs ++++ b/syscare/src/main.rs +@@ -17,7 +17,7 @@ use std::{env, ffi::OsString, process}; + use anyhow::{Context, Result}; + use args::SubCommand; + use flexi_logger::{LogSpecification, Logger, WriteMode}; +-use log::{debug, LevelFilter}; ++use log::{debug, error, LevelFilter}; + + use syscare_common::{concat_os, ffi::OsStrExt, os}; + +@@ -44,7 +44,20 @@ fn exec_external_cmd(mut args: Vec) -> ! { + let exit_status = process::Command::new(&program).args(&args).status(); + let exit_code = match exit_status { + Ok(status) => status.code().unwrap_or(1), +- Err(e) => e.raw_os_error().unwrap_or(1), ++ Err(e) => { ++ match e.kind() { ++ std::io::ErrorKind::NotFound => error!( ++ "Error: External command '{}' is not installed", ++ program.to_string_lossy() ++ ), ++ _ => error!( ++ "Error: Failed to execute '{}': {}", ++ program.to_string_lossy(), ++ e.to_string().to_lowercase() ++ ), ++ } ++ e.raw_os_error().unwrap_or(1) ++ } + }; + + process::exit(exit_code); +-- +2.43.0 + diff --git a/0151-upatch-helper-stop-compiler-replacing-atomic-ops-wit.patch b/0151-upatch-helper-stop-compiler-replacing-atomic-ops-wit.patch new file mode 100644 index 0000000000000000000000000000000000000000..22006290ac10aeeab3a743158d32eab61a486ea7 --- /dev/null +++ b/0151-upatch-helper-stop-compiler-replacing-atomic-ops-wit.patch @@ -0,0 +1,43 @@ +From 10156b26af7898034744652fe07ab465d9008f83 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Tue, 12 Aug 2025 17:18:26 +0800 +Subject: [PATCH] upatch-helper: stop compiler replacing atomic ops with + function calls on aarch64 + +Signed-off-by: renoseven +--- + upatch-helper/src/main.rs | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/upatch-helper/src/main.rs b/upatch-helper/src/main.rs +index 7c7b2b8..36eb3b0 100644 +--- a/upatch-helper/src/main.rs ++++ b/upatch-helper/src/main.rs +@@ -66,6 +66,16 @@ const COMPILE_OPTIONS_CLANG: &[&str] = &[ + + const UPATCH_ID_PREFIX: &str = ".upatch_"; + ++#[cfg(target_arch = "aarch64")] ++fn arch_specific_args() -> &'static [&'static str] { ++ &["-mno-outline-atomics"] ++} ++ ++#[cfg(not(any(target_arch = "aarch64")))] ++fn arch_specific_args() -> &'static [&'static str] { ++ &[] ++} ++ + #[inline(always)] + fn is_compilation(args: &[OsString]) -> bool { + /* check exclude flags */ +@@ -167,6 +177,7 @@ fn add_compile_options(command: &mut Command) { + }; + let assembler_arg = format!("-Wa,--defsym,{}{}=0", UPATCH_ID_PREFIX, Uuid::new_v4()); + ++ command.args(self::arch_specific_args()); + command.args(compiler_args); + command.arg(assembler_arg); + } +-- +2.43.0 + diff --git a/0152-upatch-manage-fix-stack-check-length-judge-error.patch b/0152-upatch-manage-fix-stack-check-length-judge-error.patch new file mode 100644 index 0000000000000000000000000000000000000000..c870b2ec34bc293047284b2f109e5bc6af4560f6 --- /dev/null +++ b/0152-upatch-manage-fix-stack-check-length-judge-error.patch @@ -0,0 +1,25 @@ +From 593472d09ad1b8927c27092d083c93934cb09ac4 Mon Sep 17 00:00:00 2001 +From: qiaojijun +Date: Wed, 13 Aug 2025 15:47:29 +0800 +Subject: [PATCH] upatch-manage: fix stack check length judge error + +--- + upatch-manage/upatch-stack-check.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/upatch-manage/upatch-stack-check.c b/upatch-manage/upatch-stack-check.c +index 474d857..06de7d3 100644 +--- a/upatch-manage/upatch-stack-check.c ++++ b/upatch-manage/upatch-stack-check.c +@@ -29,7 +29,7 @@ static int stack_check(struct upatch_info *uinfo, unsigned long pc, upatch_actio + log_error("Unknown upatch action\n"); + return -1; + } +- if (pc >= start && pc <= end) { ++ if (pc >= start && pc < end) { + log_error("Failed to check stack, running function: %s\n", + uinfo->funcs[i].name); + return -1; +-- +2.43.0 + diff --git a/0157-common-fix-compile-failure-on-rust-1.89.patch b/0157-common-fix-compile-failure-on-rust-1.89.patch new file mode 100644 index 0000000000000000000000000000000000000000..45488e3879dff13e7426161dce1eca97ea6f484c --- /dev/null +++ b/0157-common-fix-compile-failure-on-rust-1.89.patch @@ -0,0 +1,26 @@ +From 9d2c23734a759228fa3770bf6b179f10e75175b5 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 30 Aug 2025 15:18:49 +0800 +Subject: [PATCH] common: fix compile failure on rust 1.89 + +Signed-off-by: renoseven +--- + syscare-common/src/ffi/os_str.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/syscare-common/src/ffi/os_str.rs b/syscare-common/src/ffi/os_str.rs +index 0c414e1..1e61fe9 100644 +--- a/syscare-common/src/ffi/os_str.rs ++++ b/syscare-common/src/ffi/os_str.rs +@@ -141,7 +141,7 @@ pub trait OsStrExt: AsRef { + }) + } + +- fn split_whitespace(&self) -> Filter, FilterFn> { ++ fn split_whitespace(&self) -> Filter, FilterFn> { + self.split(SplitFn::from(char::is_whitespace)) + .filter(|s| !s.is_empty()) + } +-- +2.43.0 + diff --git a/0158-upatch-build-fix-build-failure-on-rust-1.89.patch b/0158-upatch-build-fix-build-failure-on-rust-1.89.patch new file mode 100644 index 0000000000000000000000000000000000000000..4c613f19a492912f3fbb0cfade0aebdc263f7687 --- /dev/null +++ b/0158-upatch-build-fix-build-failure-on-rust-1.89.patch @@ -0,0 +1,86 @@ +From 61641f863e511180f99f929dab9e46522b7ab8d5 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 30 Aug 2025 15:17:54 +0800 +Subject: [PATCH] upatch-build: fix build failure on rust 1.89 + +Signed-off-by: renoseven +--- + upatch-build/src/dwarf.rs | 2 +- + upatch-build/src/elf/read/elfs.rs | 6 +++--- + upatch-build/src/elf/write/elfs.rs | 2 +- + upatch-build/src/file_relation.rs | 6 ------ + 4 files changed, 5 insertions(+), 11 deletions(-) + +diff --git a/upatch-build/src/dwarf.rs b/upatch-build/src/dwarf.rs +index fe0821c..a643556 100644 +--- a/upatch-build/src/dwarf.rs ++++ b/upatch-build/src/dwarf.rs +@@ -304,7 +304,7 @@ pub struct ProducerIterator { + } + + impl> ProducerIterator { +- fn current(&self) -> Result, DebuggingInformationEntry)>> { ++ fn current(&self) -> Result, DebuggingInformationEntry<'_, '_, R>)>> { + if let Some((unit, offsets)) = &self.state { + if let Some(offset) = offsets.last() { + return Ok(Some((unit, unit.entry(*offset)?))); +diff --git a/upatch-build/src/elf/read/elfs.rs b/upatch-build/src/elf/read/elfs.rs +index 52c5e6b..e6c65bb 100644 +--- a/upatch-build/src/elf/read/elfs.rs ++++ b/upatch-build/src/elf/read/elfs.rs +@@ -41,11 +41,11 @@ impl Elf { + Ok(Self { mmap, endian }) + } + +- pub fn header(&self) -> Result

{ ++ pub fn header(&self) -> Result> { + Ok(Header::from(&self.mmap, self.endian)) + } + +- pub fn sections(&self) -> Result { ++ pub fn sections(&self) -> Result> { + let header = self.header()?; + let offset = header.get_e_shoff() as usize; + let num = header.get_e_shnum() as usize; +@@ -59,7 +59,7 @@ impl Elf { + )) + } + +- pub fn symbols(&self) -> Result { ++ pub fn symbols(&self) -> Result> { + let sections = self.sections()?; + for section in sections.clone() { + if section.get_sh_type().eq(&SHT_SYMTAB) { +diff --git a/upatch-build/src/elf/write/elfs.rs b/upatch-build/src/elf/write/elfs.rs +index 9ce2847..c2ea121 100644 +--- a/upatch-build/src/elf/write/elfs.rs ++++ b/upatch-build/src/elf/write/elfs.rs +@@ -70,7 +70,7 @@ impl Elf { + Ok(res) + } + +- pub fn symbols(&mut self) -> Result { ++ pub fn symbols(&mut self) -> Result> { + let sections = &self.sections()?; + for section in sections { + if section.get_sh_type().eq(&SHT_SYMTAB) { +diff --git a/upatch-build/src/file_relation.rs b/upatch-build/src/file_relation.rs +index 677cb8b..c7d941e 100644 +--- a/upatch-build/src/file_relation.rs ++++ b/upatch-build/src/file_relation.rs +@@ -32,12 +32,6 @@ const UPATCH_ID_PREFIX: &str = ".upatch_"; + + const NON_EXIST_PATH: &str = "/dev/null"; + +-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +-pub struct ObjectRelation { +- pub original_object: PathBuf, +- pub patched_object: PathBuf, +-} +- + /* + * The task of this class is to find out: + * 1. relationship between binary and debuginfo +-- +2.43.0 + diff --git a/0159-syscare-build-fix-compile-failure-on-rust-1.89.patch b/0159-syscare-build-fix-compile-failure-on-rust-1.89.patch new file mode 100644 index 0000000000000000000000000000000000000000..938ce77638339105f520195b4ac5ab955d6ed921 --- /dev/null +++ b/0159-syscare-build-fix-compile-failure-on-rust-1.89.patch @@ -0,0 +1,50 @@ +From 7298cf27dc5a368c51767fb49a9c15fc913c5890 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 30 Aug 2025 15:19:20 +0800 +Subject: [PATCH] syscare-build: fix compile failure on rust 1.89 + +Signed-off-by: renoseven +--- + syscare-build/src/package/rpm/tags/attr.rs | 24 ---------------------- + 1 file changed, 24 deletions(-) + +diff --git a/syscare-build/src/package/rpm/tags/attr.rs b/syscare-build/src/package/rpm/tags/attr.rs +index ef226d7..b836e02 100644 +--- a/syscare-build/src/package/rpm/tags/attr.rs ++++ b/syscare-build/src/package/rpm/tags/attr.rs +@@ -29,32 +29,8 @@ impl std::fmt::Display for RpmDefAttr { + } + } + +-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +-pub struct RpmAttr { +- pub mode: u32, +- pub user: String, +- pub group: String, +-} +- +-impl std::fmt::Display for RpmAttr { +- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- f.write_fmt(format_args!( +- "%attr({:o},{},{})", +- self.mode, self.user, self.group +- )) +- } +-} +- + #[test] + fn test() { +- let attr = RpmAttr { +- mode: 0o755, +- user: String::from("root"), +- group: String::from("nobody"), +- }; +- println!("RpmAttr::new()\n{}\n", attr); +- assert_eq!(attr.to_string(), "%attr(755,root,nobody)"); +- + let def_attr = RpmDefAttr { + file_mode: 0o755, + user: String::from("root"), +-- +2.43.0 + diff --git a/0160-syscared-apply-cargo-fmt-suggestions.patch b/0160-syscared-apply-cargo-fmt-suggestions.patch new file mode 100644 index 0000000000000000000000000000000000000000..ff8e32f4390f31a087dcbc80d0aebaf2c0484d37 --- /dev/null +++ b/0160-syscared-apply-cargo-fmt-suggestions.patch @@ -0,0 +1,28 @@ +From c60d10dba663a8760394cb9cca17fd5a07f4d813 Mon Sep 17 00:00:00 2001 +From: renoseven +Date: Sat, 30 Aug 2025 15:19:37 +0800 +Subject: [PATCH] syscared: apply cargo fmt suggestions + +Signed-off-by: renoseven +--- + syscared/src/patch/driver/upatch/target.rs | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs +index be5aabc..ff35d2f 100644 +--- a/syscared/src/patch/driver/upatch/target.rs ++++ b/syscared/src/patch/driver/upatch/target.rs +@@ -24,8 +24,8 @@ use crate::patch::entity::UserPatch; + + #[derive(Debug, Default)] + pub struct PatchTarget { +- process_map: HashMap>, // pid -> patch list +- patch_map: IndexMap, // uuid -> patch file ++ process_map: HashMap>, // pid -> patch list ++ patch_map: IndexMap, // uuid -> patch file + collision_map: HashMap>, // function old addr -> patch collision list + } + +-- +2.43.0 + diff --git a/syscare.spec b/syscare.spec index 2a4b95a93e561cb9c795366227dbc6935c9527d6..0cb73e447b3f99cae7b7662ae0368f4c6c179f49 100644 --- a/syscare.spec +++ b/syscare.spec @@ -5,7 +5,7 @@ ############################################ Name: syscare Version: 1.2.2 -Release: 6 +Release: 7 Summary: System hot-fix service License: MulanPSL-2.0 and GPL-2.0-only URL: https://gitee.com/openeuler/syscare @@ -150,6 +150,36 @@ Patch0127: 0127-CMake-fix-Werror-cast-align-for-riscv64.patch Patch0128: 0128-upatch-build-fix-compile-failure-for-lower-rust-vers.patch Patch0129: 0129-metadata-viewer-add-component.patch Patch0130: 0130-metadata-generator-add-component.patch +Patch0131: 0131-project-update-Cargo.lock.patch +Patch0132: 0132-upatch-build-rename-keep-line-macros-to-override-lin.patch +Patch0133: 0133-syscare-build-rename-keep-line-macros-to-override-li.patch +Patch0134: 0134-common-update-component.patch +Patch0135: 0135-syscared-adapt-common-crate-change.patch +Patch0136: 0136-syscared-rewrite-patch-parsing-management.patch +Patch0137: 0137-project-update-Cargo.lock.patch +Patch0138: 0138-project-update-CMakeLists.txt.patch +Patch0139: 0139-upatch-diff-fix-special-section-cannot-be-correlated.patch +Patch0140: 0140-upatch-helper-fix-compilation-command-detection.patch +Patch0141: 0141-upatch-build-use-last-write-wins-strategy-to-solving.patch +Patch0142: 0142-syscare-build-check-source-package-version-release-m.patch +Patch0143: 0143-syscare-build-match-source-package-to-debuginfo-pack.patch +Patch0144: 0144-syscare-build-fix-patch-file-list-disorder-when-buil.patch +Patch0145: 0145-project-fix-version-does-not-affect-on-rust-executab.patch +Patch0146: 0146-syscared-fix-active-deactive-process-list-calculatio.patch +Patch0147: 0147-syscared-fix-new-process-patch-active-disorder-issue.patch +Patch0148: 0148-syscare-return-error-if-external-command-is-an-inval.patch +Patch0149: 0149-syscare-use-os-error-code-for-external-command-failu.patch +Patch0150: 0150-syscare-add-error-log-when-external-command-exec-fai.patch +Patch0151: 0151-upatch-helper-stop-compiler-replacing-atomic-ops-wit.patch +Patch0152: 0152-upatch-manage-fix-stack-check-length-judge-error.patch +# Patch0153: 0153-doc-add-syscare-user-guide.patch +# Patch0154: 0154-doc-remove-non-existing-packages-in-Installation-sec.patch +# Patch0155: 0155-doc-fix-version-error-in-Installation-section-and-Us.patch +# Patch0156: 0156-doc-add-a-blank-line-after-headings-in-Introduction-.patch +Patch0157: 0157-common-fix-compile-failure-on-rust-1.89.patch +Patch0158: 0158-upatch-build-fix-build-failure-on-rust-1.89.patch +Patch0159: 0159-syscare-build-fix-compile-failure-on-rust-1.89.patch +Patch0160: 0160-syscared-apply-cargo-fmt-suggestions.patch ############### Description ################ %description @@ -258,6 +288,18 @@ Syscare patch building toolset. ################ Change log ################ ############################################ %changelog +* Sat Aug 30 2025 renoseven - 1.2.2-7 +- change: add debuginfo package version verification in syscare-build +- change: rename "--keep-line-macros" to "--override-line-macros" in syscare-build +- change: rename "--keep-line-macros" to "--override-line-macros" in upatch-build +- bugfix: fix special section correlation failure +- bugfix: fix compilation command misdetection +- bugfix: fix incorrect upatch object lookup when upatch ID conflicts +- bugfix: fix patch file disorder when building cumulative patches +- bugfix: fix abnormal syscare CLI return value when external command failure +- bugfix: fix stack check false warnings under boundary conditions +- bugfix: fix build failure under Rust 1.89 + * Tue Jun 03 2025 renoseven - 1.2.2-6 - feature: upatch supports overridding line macros - bugfix: fix upatch id parsing error