From 0734df06b21765a21ad75f088b9823b7404cb6c8 Mon Sep 17 00:00:00 2001 From: Liu Yuntao Date: Tue, 24 Oct 2023 16:49:27 +0800 Subject: [PATCH] upload libs and modify Makefile --- .gitignore | 1 - Makefile | 40 +- libs/rustlib/Cargo.toml | 23 ++ libs/rustlib/src/condition.rs | 541 ++++++++++++++++++++++++++++ libs/rustlib/src/conf_parser.rs | 188 ++++++++++ libs/rustlib/src/device.rs | 73 ++++ libs/rustlib/src/env_cargo.rs | 35 ++ libs/rustlib/src/errno_util.rs | 35 ++ libs/rustlib/src/error.rs | 78 ++++ libs/rustlib/src/fd_util.rs | 188 ++++++++++ libs/rustlib/src/file_util.rs | 29 ++ libs/rustlib/src/fs_util.rs | 77 ++++ libs/rustlib/src/io_util.rs | 60 +++ libs/rustlib/src/lib.rs | 40 ++ libs/rustlib/src/logger.rs | 209 +++++++++++ libs/rustlib/src/macros.rs | 43 +++ libs/rustlib/src/mount_util.rs | 42 +++ libs/rustlib/src/parse_util.rs | 90 +++++ libs/rustlib/src/path_lookup.rs | 126 +++++++ libs/rustlib/src/path_util.rs | 38 ++ libs/rustlib/src/proc_cmdline.rs | 104 ++++++ libs/rustlib/src/process_util.rs | 242 +++++++++++++ libs/rustlib/src/rlimit_util.rs | 49 +++ libs/rustlib/src/security.rs | 132 +++++++ libs/rustlib/src/show_table.rs | 505 ++++++++++++++++++++++++++ libs/rustlib/src/socket_util.rs | 90 +++++ libs/rustlib/src/special.rs | 39 ++ libs/rustlib/src/stat_util.rs | 52 +++ libs/rustlib/src/string.rs | 22 ++ libs/rustlib/src/time_util.rs | 27 ++ libs/rustlib/src/user_group_util.rs | 171 +++++++++ libs/rustlib/src/virtualize.rs | 74 ++++ 32 files changed, 3445 insertions(+), 18 deletions(-) create mode 100644 libs/rustlib/Cargo.toml create mode 100644 libs/rustlib/src/condition.rs create mode 100644 libs/rustlib/src/conf_parser.rs create mode 100644 libs/rustlib/src/device.rs create mode 100644 libs/rustlib/src/env_cargo.rs create mode 100644 libs/rustlib/src/errno_util.rs create mode 100644 libs/rustlib/src/error.rs create mode 100644 libs/rustlib/src/fd_util.rs create mode 100644 libs/rustlib/src/file_util.rs create mode 100644 libs/rustlib/src/fs_util.rs create mode 100644 libs/rustlib/src/io_util.rs create mode 100644 libs/rustlib/src/lib.rs create mode 100644 libs/rustlib/src/logger.rs create mode 100644 libs/rustlib/src/macros.rs create mode 100644 libs/rustlib/src/mount_util.rs create mode 100644 libs/rustlib/src/parse_util.rs create mode 100644 libs/rustlib/src/path_lookup.rs create mode 100644 libs/rustlib/src/path_util.rs create mode 100644 libs/rustlib/src/proc_cmdline.rs create mode 100644 libs/rustlib/src/process_util.rs create mode 100644 libs/rustlib/src/rlimit_util.rs create mode 100644 libs/rustlib/src/security.rs create mode 100644 libs/rustlib/src/show_table.rs create mode 100644 libs/rustlib/src/socket_util.rs create mode 100644 libs/rustlib/src/special.rs create mode 100644 libs/rustlib/src/stat_util.rs create mode 100644 libs/rustlib/src/string.rs create mode 100644 libs/rustlib/src/time_util.rs create mode 100644 libs/rustlib/src/user_group_util.rs create mode 100644 libs/rustlib/src/virtualize.rs diff --git a/.gitignore b/.gitignore index 1f546ea..0d1829d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ target ~* .vscode/ .idea/ -libs vendor Cargo.lock diff --git a/Makefile b/Makefile index 75c9107..d0ac455 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,20 @@ .PHONY: all clean test -ROOT_DIR=. -SYSBOOSTD=$(ROOT_DIR)/target/debug/sysboostd -BUILD_DIR=$(ROOT_DIR)/build -SYSBOOST=$(BUILD_DIR)/src/sysboost -SYSBOOSTD_INSTALL_PATH=/usr/bin/sysboostd -SYSBOOST_INSTALL_PATH=/usr/bin/sysboost +BUILD_DIR=build +ELFMERGE=$(BUILD_DIR)/src/elfmerge/elfmerge +ELFMERGE_INSTALL_PATH=/usr/bin/elfmerge -all: sysboostd sysboost binfmt_rto +all: sysboostd elfmerge sysboost_loader sysboostd: - clear - cargo build + cd src/sysboostd && cargo build -sysboost: +elfmerge: + meson build --buildtype=debug ninja -C build -v -binfmt_rto: - make -C src/binfmt_rto || true +sysboost_loader: + cd src/sysboost_loader && make -j8 release: rm -rf Cargo.lock @@ -30,17 +27,26 @@ debug: meson build --buildtype=debug clean: - rm -rf Cargo.lock - ninja -C build clean - cargo clean + rm -rf build + rm -rf src/sysboostd/Cargo.lock + cd src/sysboostd && cargo clean + cd src/sysboost_loader && make clean format: meson --internal clangformat ./ ./build cargo fmt install: - cp -f $(SYSBOOSTD) $(SYSBOOSTD_INSTALL_PATH) - cp -f $(SYSBOOST) $(SYSBOOST_INSTALL_PATH) + cp -f src/sysboostd/target/debug/sysboostd /usr/bin/ + cp -f $(ELFMERGE) $(ELFMERGE_INSTALL_PATH) + mkdir -p /lib/modules/sysboost/ + cp -f src/sysboost_loader/sysboost_loader.ko /lib/modules/sysboost/ + cp -f src/sysboost.service/sysboostd_exec_stop.sh /etc/systemd/system/ + cp -f src/sysboost.service/sysboost.service /usr/lib/systemd/system/ + mkdir -p /etc/sysboost.d + mkdir -p /usr/lib/relocation + xz -k $(BUILD_DIR)/src/static_template/sysboost_static_template + mv -f $(BUILD_DIR)/src/static_template/sysboost_static_template.xz /usr/lib/relocation/ test: sysboostd install clear diff --git a/libs/rustlib/Cargo.toml b/libs/rustlib/Cargo.toml new file mode 100644 index 0000000..b704a44 --- /dev/null +++ b/libs/rustlib/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "basic" +version = "0.1.0" +authors = ["overweight "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0.130" +libc = "0.2.140" +libmount = "0.1.15" +log = "0.4" +log4rs = "1.0" +snafu = "0.7" +procfs = "0.12.0" +nix = "0.24" +pathdiff = "0.2.1" +caps = "0.5.5" +lazy_static = "1.4.0" + +[features] +selinux = [] diff --git a/libs/rustlib/src/condition.rs b/libs/rustlib/src/condition.rs new file mode 100644 index 0000000..c549dec --- /dev/null +++ b/libs/rustlib/src/condition.rs @@ -0,0 +1,541 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils to test the conditions +use nix::{ + fcntl::{open, OFlag}, + sys::{ + stat, + statvfs::{fstatvfs, FsFlags}, + }, +}; + +use libc::{glob, glob_t, GLOB_NOSORT}; +#[cfg(not(target_env = "musl"))] +use libc::{statx, STATX_ATTR_MOUNT_ROOT}; + +use crate::{device::on_ac_power, fd_util, proc_cmdline, security, user_group_util}; +use std::{ + ffi::CString, + fs::File, + io::{BufRead, BufReader}, + path::Path, + str::FromStr, + string::String, +}; + +/// the type of the condition +#[derive(Eq, PartialEq)] +pub enum ConditionType { + /// check whether the service manager is running on AC Power. + ACPower, + /// check the capability + Capability, + /// check if the directory is empty + DirectoryNotEmpty, + /// check if the file is executable + FileIsExecutable, + /// check file is empty + FileNotEmpty, + /// conditionalize units on whether the system is booting up for the first time + FirstBoot, + /// check the kernel cmdline + KernelCommandLine, + /// check need update + NeedsUpdate, + /// check path exist + PathExists, + /// check if the path exists using glob pattern + PathExistsGlob, + /// check if the path is directory + PathIsDirectory, + /// check if the path is a mount point + PathIsMountPoint, + /// check path is readable and writable + PathIsReadWrite, + /// check if the path is symbolic link + PathIsSymbolicLink, + /// check the security + Security, + /// check whether the service manager is running as the given user. + User, +} + +/// check whether the condition is met. +/// if the condition start with '|', trigger it and as long as one condition is met, return ok. +/// if the condition start with '!', indicate reverse condition. +/// others indicate usual condition +pub struct Condition { + c_type: ConditionType, + trigger: i8, + revert: i8, + params: String, +} + +impl Condition { + /// create the condition instance + pub fn new(c_type: ConditionType, trigger: i8, revert: i8, params: String) -> Self { + Condition { + c_type, + trigger, + revert, + params, + } + } + + /// return the trigger + pub fn trigger(&self) -> i8 { + self.trigger + } + + /// return the revert + pub fn revert(&self) -> i8 { + self.revert + } + + /// running the condition test + pub fn test(&self) -> bool { + // empty self.params means that the condition is not set, so the test is successful + if self.params.is_empty() { + return true; + } + let result = match self.c_type { + /* The following functions will return a positive value if check pass. */ + ConditionType::ACPower => self.test_ac_power(), + ConditionType::Capability => self.test_capability(), + ConditionType::DirectoryNotEmpty => self.test_directory_not_empty(), + ConditionType::FileIsExecutable => self.test_file_is_executable(), + ConditionType::FileNotEmpty => self.test_file_not_empty(), + ConditionType::FirstBoot => self.test_first_boot(), + ConditionType::KernelCommandLine => self.test_kernel_command_line(), + ConditionType::NeedsUpdate => self.test_needs_update(), + ConditionType::PathExists => self.test_path_exists(), + ConditionType::PathExistsGlob => self.test_path_exists_glob(), + ConditionType::PathIsDirectory => self.test_path_is_directory(), + ConditionType::PathIsMountPoint => self.test_path_is_mount_point(), + ConditionType::PathIsReadWrite => self.test_path_is_read_write(), + ConditionType::PathIsSymbolicLink => self.test_path_is_symbolic_link(), + ConditionType::Security => self.test_security(), + ConditionType::User => self.test_user(), + }; + + (result > 0) ^ (self.revert() >= 1) + } + + fn test_ac_power(&self) -> i8 { + /* params is generated from bool.to_string(), so it should + * be exactly "true", not "yes"/"on" or other words. */ + let is_true = self.params.eq("true"); + !(is_true ^ on_ac_power()) as i8 + } + + fn test_capability(&self) -> i8 { + let values = match caps::Capability::from_str(&self.params) { + Err(_) => { + log::info!("Failed to parse ConditionCapability values: {}, assuming ConditionCapability check failed", self.params); + return 0; + } + Ok(v) => v, + }; + + let file = match File::open("/proc/self/status") { + Err(_) => { + log::info!( + "Failed to open /proc/self/status, assuming ConditionCapability check failed." + ); + return 0; + } + Ok(v) => v, + }; + let reader = BufReader::new(file); + let p = "CapBnd:"; + let mut cap_bitmask: u64 = 0; + for line in reader.lines() { + let line = match line { + Err(_) => { + log::info!("Failed to read /proc/self/status, assuming ConditionCapability check failed."); + return 0; + } + Ok(v) => v, + }; + if !line.starts_with(p) { + continue; + } + match u64::from_str_radix(line.trim_start_matches(p).trim_start(), 16) { + Err(_) => { + log::info!("Failed to parse CapBnd, assuming ConditionCapability check failed"); + return 0; + } + Ok(v) => { + cap_bitmask = v; + break; + } + }; + } + + let res = cap_bitmask & values.bitmask(); + (res != 0) as i8 + } + + fn test_directory_not_empty(&self) -> i8 { + let path = Path::new(&self.params); + if path.is_file() { + return 0; + } + let mut iter = match path.read_dir() { + Err(_) => { + return 0; + } + Ok(v) => v, + }; + iter.next().is_some() as i8 + } + + fn test_file_is_executable(&self) -> i8 { + let path = Path::new(&self.params); + if path.is_dir() { + return 0; + } + let s = match stat::stat(path) { + Err(_) => { + return 0; + } + Ok(v) => v, + }; + (fd_util::stat_is_reg(s.st_mode) && (s.st_mode & 111 > 0)) as i8 + } + + fn test_file_not_empty(&self) -> i8 { + let tmp_path = Path::new(&self.params); + let result = tmp_path + .metadata() + .map(|m| if m.is_file() { m.len() > 0 } else { false }) + .unwrap_or(false); + result as i8 + } + + fn test_first_boot(&self) -> i8 { + if let Ok(ret) = proc_cmdline::proc_cmdline_get_bool("sysmaster.condition-first-boot") { + if ret { + return ret as i8; + } + } + + let result = self.params.eq("true"); + + let existed = Path::new("/run/sysmaster/first-boot").exists(); + (result == existed) as i8 + } + + fn test_kernel_command_line(&self) -> i8 { + let has_equal = self.params.contains('='); + let search_value = if has_equal { + self.params.split_once('=').unwrap().0 + } else { + &self.params + }; + let value = match proc_cmdline::cmdline_get_item(search_value) { + Err(_) => { + log::info!("Failed to get cmdline content, assuming ConditionKernelCommandLine check failed."); + return 0; + } + Ok(v) => { + if v.is_none() { + log::info!( + "/proc/cmdline doesn't contain the given item: {}", + search_value + ); + return 0; + } + v.unwrap() + } + }; + log::debug!("Found kernel command line value: {value}"); + if has_equal { + /* has an equal, "crashkernel=512M matches crashkernel=512M" */ + self.params.eq(&value) as i8 + } else { + /* Check if the value has an equal */ + match value.split_once('=') { + /* doesn't has an equal, "rd matches rd" */ + None => self.params.eq(&value) as i8, + /* has an equal, "crashkernel matches crashkernel=512M" */ + Some(v) => self.params.eq(v.0) as i8, + } + } + } + + fn test_needs_update(&self) -> i8 { + 0 + } + + fn test_path_exists(&self) -> i8 { + let tmp_path = Path::new(&self.params); + let result = tmp_path.exists(); + result as i8 + } + + fn test_path_exists_glob(&self) -> i8 { + let pattern = CString::new(self.params.as_str()).unwrap(); + let mut pglob: glob_t = unsafe { std::mem::zeroed() }; + let status = unsafe { + /* use GLOB_NOSORT to speed up. */ + glob(pattern.as_ptr(), GLOB_NOSORT, None, &mut pglob) + }; + (status == 0) as i8 + } + + fn test_path_is_directory(&self) -> i8 { + Path::new(&self.params).is_dir() as i8 + } + + #[cfg(not(target_env = "musl"))] + fn test_path_is_mount_point(&self) -> i8 { + if self.params.eq("/") { + return 1; + } + /*let file = match File::open(Path::new(&self.params)) { + Err(_) => { + return 0; + } + Ok(v) => v, + }; + let fd = std::os::fd::AsRawFd::as_raw_fd(&file);*/ + let fd = 0; + let path_name = CString::new(self.params.as_str()).unwrap(); + let mut statxbuf: statx = unsafe { std::mem::zeroed() }; + unsafe { + /* statx was added to linux in kernel 4.11 per `stat(2)`, + * we can depend on it safely. So we only use statx to + * check if the path is a mount point, and chase the + * symlink unconditionally*/ + statx(fd, path_name.as_ptr(), 0, 0, &mut statxbuf); + /* The mask is supported and is set */ + i8::from( + statxbuf.stx_attributes_mask & (STATX_ATTR_MOUNT_ROOT as u64) != 0 + && statxbuf.stx_attributes & (STATX_ATTR_MOUNT_ROOT as u64) != 0, + ) + } + } + + #[cfg(target_env = "musl")] + /* musl can't use statx, check /proc/self/mountinfo. */ + fn test_path_is_mount_point(&self) -> i8 { + use libmount::mountinfo; + use std::io::Read; + + let mut mount_data = String::new(); + let mut file = match File::open("/proc/self/mountinfo") { + Err(_) => { + return 0; + } + Ok(v) => v, + }; + if file.read_to_string(&mut mount_data).is_err() { + return 0; + } + let parser = mountinfo::Parser::new(mount_data.as_bytes()); + for mount_result in parser { + if let Ok(mount) = mount_result { + let mount_point = match mount.mount_point.to_str() { + None => { + continue; + } + Some(v) => v, + }; + if self.params == mount_point { + return 1; + } + } + } + 0 + } + + fn test_path_is_read_write(&self) -> i8 { + let path = Path::new(&self.params); + if !path.exists() { + return 0; + } + let fd = match open(path, OFlag::O_CLOEXEC | OFlag::O_PATH, stat::Mode::empty()) { + Err(e) => { + log::error!( + "Failed to open {} for checking file system permission: {}", + self.params, + e + ); + return 0; + } + Ok(v) => v, + }; + if fd < 0 { + log::error!("Invalid file descriptor."); + return 0; + } + let flags = match fstatvfs(&fd) { + Err(e) => { + log::error!("Failed to get the stat of file system: {}", e); + return 0; + } + Ok(v) => v, + }; + (!flags.flags().contains(FsFlags::ST_RDONLY)) as i8 + } + + fn test_path_is_symbolic_link(&self) -> i8 { + Path::new(&self.params).is_symlink() as i8 + } + + fn test_security(&self) -> i8 { + let res = match self.params.as_str() { + "selinux" => security::selinux_enabled(), + "apparmor" => security::apparmor_enabled(), + "tomoyo" => security::tomoyo_enabled(), + "ima" => security::ima_enabled(), + "smack" => security::smack_enabled(), + "audit" => security::audit_enabled(), + "uefi-secureboot" => security::uefi_secureboot_enabled(), + "tpm2" => security::tpm2_enabled(), + _ => false, + }; + res as i8 + } + + fn test_user(&self) -> i8 { + // may be UID + if let Ok(user) = user_group_util::parse_uid(&self.params) { + return (user.uid == nix::unistd::getuid() || user.uid == nix::unistd::geteuid()) as i8; + } + + if self.params.eq("@system") { + return (user_group_util::uid_is_system(nix::unistd::getuid()) + || user_group_util::uid_is_system(nix::unistd::geteuid())) + as i8; + } + + // may be username + let result = match user_group_util::parse_name(&self.params) { + Ok(user) => user.uid == nix::unistd::getuid() || user.uid == nix::unistd::geteuid(), + _ => false, + }; + result as i8 + } +} + +#[cfg(test)] +mod test { + use crate::{logger, proc_cmdline}; + use libtests::get_project_root; + use std::path::Path; + + use super::{Condition, ConditionType}; + + #[test] + fn test_condition_test() { + logger::init_log_to_console("test_init_lookup_paths", log::LevelFilter::Debug); + let project_root = get_project_root().unwrap(); + let cond_path_not_exists = + Condition::new(ConditionType::PathExists, 0, 0, "/home/test".to_string()); + let f_result = cond_path_not_exists.test(); + assert!(!f_result); + log::debug!("project root {:?}", project_root); + let cond_path_exists = Condition::new( + ConditionType::PathExists, + 0, + 0, + project_root.to_str().unwrap().to_string(), + ); + let t_result = cond_path_exists.test(); + assert!(t_result, "condition_path exists is not true"); + let cond_path_exists_revert = Condition::new( + ConditionType::PathExists, + 0, + 1, + project_root.to_str().unwrap().to_string(), + ); + let f_result = cond_path_exists_revert.test(); + assert!(!f_result, "condition test path exist revert error"); + let cond_file_not_empty = Condition::new( + ConditionType::FileNotEmpty, + 0, + 0, + project_root.to_str().unwrap().to_string() + "/Cargo.lock", + ); + assert!(cond_file_not_empty.test(), "cond test file not empty"); + + let cond_file_empty = Condition::new( + ConditionType::FileNotEmpty, + 0, + 0, + project_root.to_str().unwrap().to_string(), + ); + assert!(!cond_file_empty.test(), "cond test file empty"); + } + + #[test] + fn test_condition_user() { + if nix::unistd::getuid() != nix::unistd::Uid::from_raw(0) { + return; + } + + let root_user = "root"; + let cond_user_root_username = + Condition::new(ConditionType::User, 0, 0, root_user.to_string()); + assert!(cond_user_root_username.test(), "cond root username"); + + let root_user_num = "0"; + let cond_user_root_username_num = + Condition::new(ConditionType::User, 0, 0, root_user_num.to_string()); + assert!(cond_user_root_username_num.test(), "cond root username"); + + let fake_user = "fake"; + let cond_user_fake_username = + Condition::new(ConditionType::User, 0, 0, fake_user.to_string()); + assert!(!cond_user_fake_username.test(), "cond fake username"); + + let fake_user_num = "1234"; + let cond_user_fake_username_num = + Condition::new(ConditionType::User, 0, 0, fake_user_num.to_string()); + assert!(!cond_user_fake_username_num.test(), "cond fake username"); + + let system_str = "@system"; + let cond_user_system_str = + Condition::new(ConditionType::User, 0, 0, system_str.to_string()); + assert!(cond_user_system_str.test(), "cond system username"); + } + + #[test] + fn test_condition_first_boot() { + if let Ok(ret) = proc_cmdline::proc_cmdline_get_bool("sysmaster.condition-first-boot") { + if ret { + println!( + "this test cannot be tested because we cannot modify the kernel parameters" + ); + return; + } + } + + let existed = Path::new("/run/sysmaster/first-boot").exists(); + let cond_first_boot_true = + Condition::new(ConditionType::FirstBoot, 0, 0, String::from("true")); + let cond_first_boot_false = + Condition::new(ConditionType::FirstBoot, 0, 0, String::from("false")); + if existed { + println!("file is existed"); + assert!(cond_first_boot_true.test(), "file should be existed"); + assert!(!cond_first_boot_false.test(), "file should be existed"); + } else { + println!("file is no existed"); + assert!(!cond_first_boot_true.test(), "file should not be existed"); + assert!(cond_first_boot_false.test(), "file should not be existed"); + } + } +} diff --git a/libs/rustlib/src/conf_parser.rs b/libs/rustlib/src/conf_parser.rs new file mode 100644 index 0000000..dd733ac --- /dev/null +++ b/libs/rustlib/src/conf_parser.rs @@ -0,0 +1,188 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils can be used to parse unit conf file +use crate::error::*; + +/// the base that will be translate from str +#[derive(Debug, Eq, PartialEq)] +pub enum Base { + /// binary number + Binary, + /// Decimal number + Decimal, +} + +/// return true if item is 1, yes, y, true, t or on +/// return false if item is 0, no, n, false, f, or off +pub fn parse_boolean(item: &str) -> Result { + match &item.to_lowercase() as &str { + "1" | "yes" | "y" | "true" | "t" | "on" => Ok(true), + "0" | "no" | "n" | "false" | "f" | "off" => Ok(false), + _ => Err(Error::Parse { + source: "wrong boolean value".into(), + }), + } +} + +/// parse the item from string to u64 +/// the item can be end with E, P, T, G, M, K and B +pub fn parse_size(item: &str, base: Base) -> Result { + let item = item.trim(); + if item.is_empty() { + return Err(Error::Parse { + source: "empty string".into(), + }); + } + + if item.starts_with('-') { + return Err(Error::Parse { + source: "invalue string".into(), + }); + } + + let binary_table = [ + ( + 'E', + 1024u64 * 1024u64 * 1024u64 * 1024u64 * 1024u64 * 1024u64, + ), + ('P', 1024u64 * 1024u64 * 1024u64 * 1024u64 * 1024u64), + ('T', 1024u64 * 1024u64 * 1024u64 * 1024u64), + ('G', 1024u64 * 1024u64 * 1024u64), + ('M', 1024u64 * 1024u64), + ('K', 1024u64), + ('B', 1u64), + (' ', 1u64), + ]; + + let decimal_table = [ + ( + 'E', + 1000u64 * 1000u64 * 1000u64 * 1000u64 * 1000u64 * 1000u64, + ), + ('P', 1000u64 * 1000u64 * 1000u64 * 1000u64 * 1000u64), + ('T', 1000u64 * 1000u64 * 1000u64 * 1000u64), + ('G', 1000u64 * 1000u64 * 1000u64), + ('M', 1000u64 * 1000u64), + ('K', 1000u64), + ('B', 1u64), + (' ', 1u64), + ]; + + let table = if base == Base::Binary { + binary_table + } else { + decimal_table + }; + + if let Ok(v) = item.parse::() { + return Ok(v as u64); + } + + let mut ret: u64 = 0; + let mut start: usize = usize::MAX; + + for (i, v) in item.as_bytes().iter().enumerate() { + if char::from(*v) == ' ' || char::from(*v) == '.' { + continue; + }; + + if char::from(*v).is_ascii_digit() { + continue; + } + + for (index, (key, _factor)) in table.iter().enumerate() { + if *key == char::from(*v) { + start = index; + break; + } + } + + if start == usize::MAX { + return Err(Error::Parse { + source: "invalid unit".into(), + }); + } + + let cur = item[..i].to_string().parse::()?; + + if cur > (u64::MAX / table[start].1) as f64 { + return Err(Error::Parse { + source: "value is out of range".into(), + }); + } + + ret = (cur * table[start].1 as f64) as u64; + } + + Ok(ret) +} + +#[cfg(test)] +mod test { + + #[test] + fn test_parse_size() { + use crate::conf_parser::{parse_size, Base}; + let ret1 = parse_size("", Base::Binary); + assert!(ret1.is_err()); + + let ret1 = parse_size("100G", Base::Binary).unwrap(); + assert_eq!(ret1, 100 * 1024 * 1024 * 1024); + + let ret1 = parse_size("99", Base::Binary).unwrap(); + assert_eq!(ret1, 99); + + let ret1 = parse_size("99.4", Base::Binary).unwrap(); + assert_eq!(ret1, 99); + + let ret1 = parse_size("4.5K", Base::Binary).unwrap(); + assert_eq!(ret1, 4 * 1024 + 512); + + let ret1 = parse_size("15E", Base::Binary).unwrap(); + assert_eq!( + ret1, + 15 * 1024u64 * 1024u64 * 1024u64 * 1024u64 * 1024u64 * 1024u64 + ); + + let ret1 = parse_size("4.5C", Base::Binary); + assert!(ret1.is_err()); + } + + #[test] + fn test_parse_boolean() { + use crate::conf_parser::parse_boolean; + + assert!(parse_boolean("1").unwrap()); + assert!(parse_boolean("y").unwrap()); + assert!(parse_boolean("Y").unwrap()); + assert!(parse_boolean("yes").unwrap()); + assert!(parse_boolean("YES").unwrap()); + assert!(parse_boolean("true").unwrap()); + assert!(parse_boolean("TRUE").unwrap()); + assert!(parse_boolean("on").unwrap()); + assert!(parse_boolean("ON").unwrap()); + + assert!(!parse_boolean("0").unwrap()); + assert!(!parse_boolean("n").unwrap()); + assert!(!parse_boolean("N").unwrap()); + assert!(!parse_boolean("no").unwrap()); + assert!(!parse_boolean("NO").unwrap()); + assert!(!parse_boolean("false").unwrap()); + assert!(!parse_boolean("FALSE").unwrap()); + assert!(!parse_boolean("off").unwrap()); + assert!(!parse_boolean("OFF").unwrap()); + + assert!(parse_boolean("process").is_err()); + assert!(parse_boolean("in").is_err()); + } +} diff --git a/libs/rustlib/src/device.rs b/libs/rustlib/src/device.rs new file mode 100644 index 0000000..264e973 --- /dev/null +++ b/libs/rustlib/src/device.rs @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! Common used device functions + +use std::path::Path; + +/// check if the system is running on AC Power +pub fn on_ac_power() -> bool { + /* 1 for true or failure, 0 for false. */ + let path = Path::new("/sys/class/power_supply"); + if !path.exists() || !path.is_dir() { + return true; + } + let des = match path.read_dir() { + Err(e) => { + log::info!("Failed to walk /sys/class/power_supply: {}, ignoring", e); + return true; + } + Ok(v) => v, + }; + let mut found_online = false; + let mut found_offline = false; + for de in des { + /* We only check the "type" file and "online" file in + * each directory, and skip whenever failed. */ + let de = match de { + Err(_) => { + continue; + } + Ok(v) => v, + }; + let de_path = path.join(de.file_name()); + // 1. The content of "type" file should be "Mains". + let contents = match std::fs::read(de_path.join("type")) { + Err(_) => { + continue; + } + Ok(v) => v, + }; + if !"Mains\n".to_string().into_bytes().eq(&contents) { + continue; + } + // 2. The content of "online" file should be "0" or "1". + let contents = match std::fs::read(de_path.join("online")) { + Err(_) => { + continue; + } + Ok(v) => v, + }; + if contents.len() != 2 || contents[1] != b'\n' { + continue; + } + if contents[0] == b'1' { + found_online = true; + break; + } else if contents[0] == b'0' { + found_offline = true; + } else { + continue; + } + } + found_online || !found_offline +} diff --git a/libs/rustlib/src/env_cargo.rs b/libs/rustlib/src/env_cargo.rs new file mode 100644 index 0000000..6ee8206 --- /dev/null +++ b/libs/rustlib/src/env_cargo.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use std::env; + +/// +pub fn env_path() -> Result { + let path = env::var("OUT_DIR") + .or_else(|_| env::var("LD_LIBRARY_PATH")) + .context(VarSnafu)?; + + let out_dir = path.split(':').collect::>()[0] + .split("target") + .collect::>()[0] + .to_string(); + let tmp_str: Vec<_> = out_dir.split("build").collect(); + if tmp_str.is_empty() { + return Err(Error::Other { + msg: "not running with cargo".to_string(), + }); + } + + Ok(tmp_str[0].to_string()) +} diff --git a/libs/rustlib/src/errno_util.rs b/libs/rustlib/src/errno_util.rs new file mode 100644 index 0000000..0e5a6c3 --- /dev/null +++ b/libs/rustlib/src/errno_util.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! utility for checking errno +//! + +use nix::errno::Errno; + +/// seven errno for "operation, system call, ioctl or socket feature not supported" +pub fn errno_is_not_supported(source: Errno) -> bool { + matches!( + source, + Errno::EOPNOTSUPP + | Errno::ENOTTY + | Errno::ENOSYS + | Errno::EAFNOSUPPORT + | Errno::EPFNOSUPPORT + | Errno::EPROTONOSUPPORT + | Errno::ESOCKTNOSUPPORT + ) +} + +/// two errno for access problems +pub fn errno_is_privilege(source: Errno) -> bool { + matches!(source, Errno::EACCES | Errno::EPERM) +} diff --git a/libs/rustlib/src/error.rs b/libs/rustlib/src/error.rs new file mode 100644 index 0000000..b30bf13 --- /dev/null +++ b/libs/rustlib/src/error.rs @@ -0,0 +1,78 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! error definitions +use snafu::prelude::*; +#[allow(unused_imports)] +pub use snafu::ResultExt; + +#[allow(missing_docs)] +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +#[non_exhaustive] +pub enum Error { + #[snafu(display( + "Got an error: (ret={}, errno={}) for syscall: {}", + ret, + errno, + syscall + ))] + Syscall { + syscall: &'static str, + ret: i32, + errno: i32, + }, + + #[snafu(display("Io"))] + Io { source: std::io::Error }, + + #[snafu(display("Errno"))] + Nix { source: nix::Error }, + + #[snafu(display("Var"))] + Var { source: std::env::VarError }, + + #[snafu(display("procfs"))] + Proc { source: procfs::ProcError }, + + #[snafu(display("Error parsing from string: {}", source))] + Parse { + source: Box, + }, + + #[snafu(display("Not exist): '{}'.", what))] + NotExisted { what: String }, + + #[snafu(display("Invalid: '{}'.", what))] + Invalid { what: String }, + + #[snafu(display("OtherError): '{}'.", msg))] + Other { msg: String }, +} + +#[allow(unused_macros)] +macro_rules! errfrom { + ($($st:ty),* => $variant:ident) => ( + $( + impl From<$st> for Error { + fn from(e: $st) -> Error { + Error::$variant { source: e.into() } + } + } + )* + ) +} + +errfrom!(std::num::ParseIntError, std::string::ParseError, std::num::ParseFloatError, std::str::ParseBoolError, std::string::FromUtf8Error => Parse); + +/// +pub type Result = std::result::Result; diff --git a/libs/rustlib/src/fd_util.rs b/libs/rustlib/src/fd_util.rs new file mode 100644 index 0000000..010602a --- /dev/null +++ b/libs/rustlib/src/fd_util.rs @@ -0,0 +1,188 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use nix::{ + errno::Errno, + fcntl::{FcntlArg, FdFlag, OFlag}, + ioctl_read, + sys::stat::SFlag, +}; + +/// check if the given stat.st_mode is regular file +pub fn stat_is_reg(st_mode: u32) -> bool { + st_mode & SFlag::S_IFMT.bits() & SFlag::S_IFREG.bits() > 0 +} + +/// check if the given stat.st_mode is char device +pub fn stat_is_char(st_mode: u32) -> bool { + st_mode & SFlag::S_IFMT.bits() & SFlag::S_IFCHR.bits() > 0 +} + +/// +pub fn fd_nonblock(fd: i32, nonblock: bool) -> Result<()> { + assert!(fd >= 0); + + let flags = nix::fcntl::fcntl(fd, FcntlArg::F_GETFL).context(NixSnafu)?; + let fd_flag = unsafe { OFlag::from_bits_unchecked(flags) }; + + let nflag = match nonblock { + true => fd_flag | OFlag::O_NONBLOCK, + false => fd_flag & !OFlag::O_NONBLOCK, + }; + + if nflag == fd_flag { + return Ok(()); + } + + nix::fcntl::fcntl(fd, FcntlArg::F_SETFL(nflag)).context(NixSnafu)?; + + Ok(()) +} + +/// +pub fn fd_cloexec(fd: i32, cloexec: bool) -> Result<()> { + assert!(fd >= 0); + + let flags = nix::fcntl::fcntl(fd, FcntlArg::F_GETFD).context(NixSnafu)?; + + let fd_flag = unsafe { FdFlag::from_bits_unchecked(flags) }; + + let nflag = match cloexec { + true => fd_flag | FdFlag::FD_CLOEXEC, + false => fd_flag & !FdFlag::FD_CLOEXEC, + }; + + nix::fcntl::fcntl(fd, FcntlArg::F_SETFD(nflag)).context(NixSnafu)?; + + Ok(()) +} + +/// +pub fn fd_is_cloexec(fd: i32) -> bool { + assert!(fd >= 0); + + let flags = nix::fcntl::fcntl(fd, FcntlArg::F_GETFD).unwrap_or(0); + let fd_flag = FdFlag::from_bits(flags).unwrap(); + fd_flag.contains(FdFlag::FD_CLOEXEC) +} + +/// +pub fn close(fd: i32) { + if let Err(e) = nix::unistd::close(fd) { + log::warn!("close fd {} failed, errno: {}", fd, e); + } +} + +/// reopen the specified fd with new flags to convert an O_PATH fd into +/// regular one, or to turn O_RDWR fds into O_RDONLY fds +/// +/// this function can not work on sockets, as they can not be opened +/// +/// note that this function implicitly reset the read index to zero +pub fn fd_reopen(fd: i32, oflags: OFlag) -> Result { + if oflags.intersects(OFlag::O_DIRECTORY) { + let new_fd = nix::fcntl::openat(fd, ".", oflags, nix::sys::stat::Mode::empty()) + .map_err(|e| Error::Nix { source: e })?; + + return Ok(new_fd); + } + + match nix::fcntl::open( + format!("/proc/self/fd/{}", fd).as_str(), + oflags, + nix::sys::stat::Mode::empty(), + ) { + Ok(n) => Ok(n), + Err(e) => { + if e != Errno::ENOENT { + return Err(Error::Nix { source: e }); + } + + if !crate::stat_util::proc_mounted().map_err(|_| Error::Nix { + source: Errno::ENOENT, + })? { + // if /proc/ is not mounted, this function can not work + Err(Error::Nix { + source: Errno::ENOSYS, + }) + } else { + // if /proc/ is mounted, means this fd is not valid + Err(Error::Nix { + source: Errno::EBADF, + }) + } + } + } +} + +const BLK_DISKSEQ_MAGIC: u8 = 18; +const BLK_GET_DISKSEQ: u8 = 128; +ioctl_read!( + /// get the diskseq from block + blk_get_diskseq, + BLK_DISKSEQ_MAGIC, + BLK_GET_DISKSEQ, + u64 +); + +/// get the diskseq according to fd +pub fn fd_get_diskseq(fd: i32) -> Result { + let mut diskseq: u64 = 0; + let ptr: *mut u64 = &mut diskseq; + unsafe { + match blk_get_diskseq(fd, ptr) { + Ok(_) => {} + Err(e) => { + if !crate::errno_util::errno_is_not_supported(e) && e != Errno::EINVAL { + return Err(Error::Nix { source: e }); + } + + return Err(Error::Nix { + source: Errno::EOPNOTSUPP, + }); + } + } + } + Ok(diskseq) +} + +#[cfg(test)] +mod tests { + use crate::fd_util::{stat_is_char, stat_is_reg}; + use nix::sys::stat::fstat; + use std::{fs::File, os::fd::AsRawFd, path::Path}; + + #[test] + fn test_stats() { + let fd_reg_file = File::open(Path::new("/bin/true")).unwrap(); + assert!(fd_reg_file.as_raw_fd() >= 0); + let st = fstat(fd_reg_file.as_raw_fd()).unwrap(); + assert!(stat_is_reg(st.st_mode)); + + let fd_non_reg_file = File::open(Path::new("/proc/1")).unwrap(); + assert!(fd_non_reg_file.as_raw_fd() >= 0); + let st = fstat(fd_non_reg_file.as_raw_fd()).unwrap(); + assert!(!stat_is_reg(st.st_mode)); + + let fd_char_file = File::open(Path::new("/dev/zero")).unwrap(); + assert!(fd_char_file.as_raw_fd() >= 0); + let st = fstat(fd_char_file.as_raw_fd()).unwrap(); + assert!(stat_is_char(st.st_mode)); + + let fd_non_char_file = File::open(Path::new("/proc/1")).unwrap(); + assert!(fd_non_char_file.as_raw_fd() >= 0); + let st = fstat(fd_non_char_file.as_raw_fd()).unwrap(); + assert!(!stat_is_char(st.st_mode)); + } +} diff --git a/libs/rustlib/src/file_util.rs b/libs/rustlib/src/file_util.rs new file mode 100644 index 0000000..2f9d789 --- /dev/null +++ b/libs/rustlib/src/file_util.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils of the file operation +//! +use crate::error::*; +use std::io::BufRead; +use std::io::BufReader; +use std::path::Path; + +/// read first line from a file +pub fn read_first_line(path: &Path) -> Result { + let file = std::fs::File::open(path).context(IoSnafu)?; + + let mut buffer = BufReader::new(file); + let mut first_line = String::with_capacity(1024); + let _ = buffer.read_line(&mut first_line); + + Ok(first_line) +} diff --git a/libs/rustlib/src/fs_util.rs b/libs/rustlib/src/fs_util.rs new file mode 100644 index 0000000..a854f1d --- /dev/null +++ b/libs/rustlib/src/fs_util.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils of the file operation +//! +use crate::error::*; +use nix::{fcntl::OFlag, sys::stat::Mode}; +use pathdiff::diff_paths; +use std::path::Path; + +/// open the parent directory of path +pub fn open_parent(path: &Path, flags: OFlag, mode: Mode) -> Result { + let parent = path.parent().ok_or(Error::Nix { + source: nix::errno::Errno::EINVAL, + })?; + + nix::fcntl::open(parent, flags, mode).context(NixSnafu) +} + +/// create symlink link_name -> target +pub fn symlink(target: &str, link: &str, relative: bool) -> Result<()> { + let link_path = Path::new(&link); + let target_path = Path::new(&target); + + let (target_path, fd) = if relative { + let link_path_parent = link_path.parent().ok_or(Error::NotExisted { + what: format!("{}'s parent", link_path.to_string_lossy()), + })?; + + let rel_path = diff_paths(target_path, link_path_parent).unwrap(); + let fd = nix::fcntl::open(&rel_path, OFlag::O_DIRECT, Mode::from_bits(0).unwrap()) + .context(NixSnafu)?; + (rel_path, Some(fd)) + } else { + (target_path.to_path_buf(), None) + }; + + nix::unistd::symlinkat(target_path.as_path(), fd, link_path).map_err(|e| { + log::debug!("Failed to create symlink: {} -> {}", link, target); + Error::Nix { source: e } + }) +} + +#[cfg(test)] +mod tests { + use crate::fs_util::symlink; + use nix::unistd; + + #[test] + fn test_symlink() { + // use a complicated long name to make sure we don't have this file + // before running this testcase. + let link_name_path = std::path::Path::new("/tmp/test_link_name_39285b"); + if link_name_path.exists() { + return; + } + + let ret = symlink("/dev/null", "/tmp/test_link_name_39285b", false); + assert!(ret.is_ok()); + + let ret = unistd::unlinkat( + None, + link_name_path.to_str().unwrap(), + unistd::UnlinkatFlags::NoRemoveDir, + ); + assert!(ret.is_ok()); + } +} diff --git a/libs/rustlib/src/io_util.rs b/libs/rustlib/src/io_util.rs new file mode 100644 index 0000000..b90d3a9 --- /dev/null +++ b/libs/rustlib/src/io_util.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use nix::{ + libc, + poll::{self, PollFd, PollFlags}, + sys::{signal::SigSet, time::TimeSpec}, +}; +use std::os::unix::prelude::RawFd; + +fn ppoll_timeout(fds: &mut [PollFd], timeout: Option) -> Result { + if fds.is_empty() { + return Ok(0); + } + + let ret = poll::ppoll(fds, timeout, SigSet::empty()).context(NixSnafu)?; + + if ret == 0 { + return Ok(0); + } + + for item in fds { + if item.revents().is_none() { + continue; + } + + if item.revents().unwrap().eq(&PollFlags::POLLNVAL) { + return Err(Error::Nix { + source: nix::errno::Errno::EBADF, + }); + } + } + + Ok(ret) +} + +/// +pub fn wait_for_events(fd: RawFd, event: PollFlags, time_out: i64) -> Result { + let poll_fd = PollFd::new(fd, event); + let time_spec = TimeSpec::from_timespec(libc::timespec { + tv_sec: time_out, + tv_nsec: 0, + }); + let mut fds = [poll_fd]; + + let ret = ppoll_timeout(&mut fds, Some(time_spec))?; + + Ok(ret) +} diff --git a/libs/rustlib/src/lib.rs b/libs/rustlib/src/lib.rs new file mode 100644 index 0000000..7f7b934 --- /dev/null +++ b/libs/rustlib/src/lib.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! +pub mod conf_parser; +pub mod device; +pub mod env_cargo; +pub mod errno_util; +pub mod error; +pub mod fd_util; +pub mod file_util; +pub mod fs_util; +pub mod io_util; +pub mod logger; +pub mod macros; +pub mod mount_util; +pub mod parse_util; +pub mod path_lookup; +pub mod path_util; +pub mod proc_cmdline; +pub mod rlimit_util; +pub mod security; +pub mod show_table; +pub mod socket_util; +pub mod special; +pub mod stat_util; +pub mod string; +pub mod time_util; +pub mod user_group_util; +pub mod virtualize; +pub use error::*; diff --git a/libs/rustlib/src/logger.rs b/libs/rustlib/src/logger.rs new file mode 100644 index 0000000..2f24d25 --- /dev/null +++ b/libs/rustlib/src/logger.rs @@ -0,0 +1,209 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 log::{LevelFilter, Log}; +use log4rs::{ + append::{ + console::{ConsoleAppender, Target}, + file::FileAppender, + Append, + }, + config::{Appender, Config, Logger, Root}, + encode::pattern::PatternEncoder, +}; +use nix::libc; + +/// sysmaster log parttern: +/// +/// ```rust,ignore +/// {d(%Y-%m-%d %H:%M:%S)} {h({l}):<5} {M} {m}{n} +/// {d(%Y-%m-%d %H:%M:%S)}: log time, i.e. `2023-03-24 11:00:23` +/// {h({l}:<5)}: log level, 5 bytes +/// {M}: the method name where the logging request was issued +/// {m}: log message +/// {n}: separator character, '\n' in linux. +/// ``` +pub const LOG_PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S)} {h({l}):<5} {M} {m}{n}"; + +struct LogPlugin(log4rs::Logger); + +impl log::Log for LogPlugin { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.0.enabled(metadata) + } + + fn log(&self, record: &log::Record) { + self.0.log(record); + } + + fn flush(&self) { + Log::flush(&self.0); + } +} + +struct SysLogger; + +/* This is an extremely simple implementation, and only + * supports the very basic log function. */ +impl log::Log for SysLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + let msg = record.args().to_string(); + let level = match record.level() { + log::Level::Error => libc::LOG_ERR, + log::Level::Warn => libc::LOG_WARNING, + log::Level::Info => libc::LOG_INFO, + log::Level::Debug => libc::LOG_DEBUG, + /* The highest libc log level is LOG_DEBUG */ + log::Level::Trace => libc::LOG_DEBUG, + }; + unsafe { + libc::syslog(level, msg.as_ptr() as *const libc::c_char); + } + } + + fn flush(&self) {} +} + +fn append_log(app_name: &str, level: LevelFilter, target: &str, file_path: Option<&str>) { + if target == "syslog" { + let _ = log::set_boxed_logger(Box::new(SysLogger)); + log::set_max_level(level); + return; + } + let config = build_log_config(app_name, level, target, file_path); + let logger = log4rs::Logger::new(config); + log::set_max_level(level); + let _ = log::set_boxed_logger(Box::new(LogPlugin(logger))); +} + +/// Init and set the sub unit manager's log +/// +/// [`app_name`]: which app output the log +/// +/// level: maximum log level +/// +/// target: log target +/// +/// file_path: file path if the target is set to file +pub fn init_log_for_subum(app_name: &str, level: LevelFilter, target: &str, file: &str) { + let file = if file.is_empty() { None } else { Some(file) }; + /* We should avoid calling init_log here, or we will get many "attempted + * to set a logger after the logging system was already initialized" error + * message. */ + append_log(app_name, level, target, file); +} + +/// Init and set the log target to console +/// +/// [`app_name`]: which app output the log +/// +/// level: maximum log level +pub fn init_log_to_console(app_name: &str, level: LevelFilter) { + init_log(app_name, level, "console", None); +} + +/// Init and set the log target to file +/// +/// [`app_name`]: which app output the log +/// +/// level: maximum log level +/// +/// file_path: log to which file +pub fn init_log_to_file(app_name: &str, level: LevelFilter, file_path: &str) { + init_log(app_name, level, "file", Some(file_path)); +} + +/// Init and set the logger +/// +/// [`app_name`]: which app output the log +/// +/// level: maximum log level +/// +/// target: log target +/// +/// file_path: file path if the target is set to file +pub fn init_log(app_name: &str, level: LevelFilter, target: &str, file_path: Option<&str>) { + if target == "syslog" { + let _ = log::set_boxed_logger(Box::new(SysLogger)); + log::set_max_level(level); + return; + } + let config = build_log_config(app_name, level, target, file_path); + let r = log4rs::init_config(config); + if let Err(e) = r { + println!("{e}"); + } +} + +fn build_log_config( + app_name: &str, + level: LevelFilter, + target: &str, + file: Option<&str>, +) -> Config { + let mut target = target; + /* If the file is configured to None, use console forcely. */ + if file.is_none() && target == "file" { + println!("LogTarget is configured to `file`, but LogFile is not configured, changing the LogTarget to `console`"); + target = "console"; + } + let encoder = Box::new(PatternEncoder::new(LOG_PATTERN)); + let appender: Box = match target { + "console" => Box::new( + ConsoleAppender::builder() + .encoder(encoder) + .target(Target::Stdout) + .build(), + ), + "file" => Box::new( + FileAppender::builder() + .encoder(encoder) + .build(file.unwrap()) + .unwrap(), + ), + _ => Box::new( + ConsoleAppender::builder() + .encoder(encoder) + .target(Target::Stdout) + .build(), + ), + }; + let logger = Logger::builder().build(app_name, level); + let root = Root::builder().appender(target).build(level); + Config::builder() + .appender(Appender::builder().build(target, appender)) + .logger(logger) + .build(root) + .unwrap() +} + +#[cfg(test)] + +mod tests { + use super::*; + use log; + #[test] + fn test_init_log_to_console() { + init_log_to_console("test", LevelFilter::Debug); + // assert_eq!((), ()); + log::info!("test for logger info"); + log::error!("test for logger error"); + log::warn!("test for logger warn"); + log::debug!("test for logger debug"); + log::trace!("test for logger trace"); + } +} diff --git a/libs/rustlib/src/macros.rs b/libs/rustlib/src/macros.rs new file mode 100644 index 0000000..2bb9ef7 --- /dev/null +++ b/libs/rustlib/src/macros.rs @@ -0,0 +1,43 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! macros + +/// syscall +#[macro_export] +macro_rules! syscall { + ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ + let res = unsafe { libc::$fn($($arg, )*) }; + if res < 0 { + basic::Result::Err(basic::Error::Syscall { syscall: stringify!($fn), errno: unsafe { *libc::__errno_location() }, ret: res }) + } else { + basic::Result::Ok(res) + } + }}; +} + +/// IN_SET +#[macro_export] +macro_rules! IN_SET { + ($ov:expr, $($nv:expr),+) => { + { + let mut found = false; + $( + if $ov == $nv { + found = true; + } + )+ + + found + } + }; +} diff --git a/libs/rustlib/src/mount_util.rs b/libs/rustlib/src/mount_util.rs new file mode 100644 index 0000000..3b041d3 --- /dev/null +++ b/libs/rustlib/src/mount_util.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use nix::{ + fcntl::AtFlags, + sys::stat::{fstatat, SFlag}, +}; + +/// +pub fn mount_point_fd_valid(fd: i32, file_name: &str, flags: AtFlags) -> Result { + assert!(fd >= 0); + + let flags = if flags.contains(AtFlags::AT_SYMLINK_FOLLOW) { + flags & !AtFlags::AT_SYMLINK_FOLLOW + } else { + flags | AtFlags::AT_SYMLINK_FOLLOW + }; + + let f_stat = fstatat(fd, file_name, flags).context(NixSnafu)?; + if SFlag::S_IFLNK.bits() & f_stat.st_mode == SFlag::S_IFLNK.bits() { + return Ok(false); + } + + let d_stat = fstatat(fd, "", AtFlags::AT_EMPTY_PATH).context(NixSnafu)?; + + if f_stat.st_dev == d_stat.st_dev && f_stat.st_ino == d_stat.st_ino { + return Ok(true); + } + + Ok(f_stat.st_dev != d_stat.st_dev) +} diff --git a/libs/rustlib/src/parse_util.rs b/libs/rustlib/src/parse_util.rs new file mode 100644 index 0000000..57a836c --- /dev/null +++ b/libs/rustlib/src/parse_util.rs @@ -0,0 +1,90 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils can be used to deal with devnum +use crate::error::*; +use nix::{ + libc::{mode_t, S_IFBLK, S_IFCHR}, + sys::stat::makedev, +}; +use std::path::Path; + +/// given a device path, extract its mode and devnum +/// e.g. input /dev/block/8:0, output (S_IFBLK, makedev(8,0)) +pub fn device_path_parse_devnum(path: String) -> Result<(mode_t, u64)> { + let mode = if path.starts_with("/dev/block/") { + S_IFBLK + } else if path.starts_with("/dev/char/") { + S_IFCHR + } else { + return Err(Error::Nix { + source: nix::errno::Errno::ENODEV, + }); + }; + + let filename = match Path::new(&path).file_name() { + Some(s) => s.to_string_lossy().to_string(), + None => { + return Err(Error::Invalid { + what: format!("invalid path {}", path), + }) + } + }; + + Ok((mode, parse_devnum(filename)?)) +} + +/// parse the major:minor like string, and return the devnum +pub fn parse_devnum(s: String) -> Result { + let tokens: Vec<&str> = s.split(':').collect(); + if tokens.len() != 2 { + return Err(Error::Invalid { + what: format!("incorrect number of tokens: {}", s), + }); + } + + let (major, minor) = ( + tokens[0].parse::().map_err(|_| Error::Invalid { + what: format!("invalid major: {}", tokens[0]), + })?, + tokens[1].parse::().map_err(|_| Error::Invalid { + what: format!("invalid minor: {}", tokens[1]), + })?, + ); + + Ok(makedev(major, minor)) +} + +/// parse the numeric like string into ifindex number +pub fn parse_ifindex(s: String) -> Result { + s.parse::().map_err(|_| Error::Nix { + source: nix::errno::Errno::EINVAL, + }) +} + +/// parse the string into mode_t +pub fn parse_mode(mode: &str) -> Result { + match mode.parse::() { + Ok(v) => { + if v > 7777 { + return Err(Error::Nix { + source: nix::errno::Errno::ERANGE, + }); + } + + Ok(v) + } + Err(e) => Err(Error::Parse { + source: Box::new(e), + }), + } +} diff --git a/libs/rustlib/src/path_lookup.rs b/libs/rustlib/src/path_lookup.rs new file mode 100644 index 0000000..c711305 --- /dev/null +++ b/libs/rustlib/src/path_lookup.rs @@ -0,0 +1,126 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the management of the unit file lookup path +use std::env; + +/// unit lookup path in /etc +pub const ETC_SYSTEM_PATH: &str = "/etc/sysmaster"; +/// unit lookup path in /run +pub const RUN_SYSTEM_PATH: &str = "/run/sysmaster"; +/// unit lookup path in /usr/lib +pub const LIB_SYSTEM_PATH: &str = "/usr/lib/sysmaster"; + +/// struct LookupPaths +#[derive(Debug, Clone)] +pub struct LookupPaths { + /// Used to search fragment, dropin, updated + pub search_path: Vec, + /// Used to search preset file + pub preset_path: Vec, + /// generator paths + pub generator: String, + /// generator early paths + pub generator_early: String, + /// generator late paths + pub generator_late: String, + /// transient paths + pub transient: String, + /// transient paths + pub persistent_path: String, +} + +impl LookupPaths { + /// new + pub fn new() -> Self { + LookupPaths { + generator: String::from(""), + generator_early: String::from(""), + generator_late: String::from(""), + transient: String::from(""), + search_path: Vec::new(), + persistent_path: String::from(""), + preset_path: Vec::new(), + } + } + + /// init lookup paths + pub fn init_lookup_paths(&mut self) { + let devel_path = || { + let out_dir = env::var("OUT_DIR").unwrap_or_else(|_x| { + let _tmp_str: Option<&'static str> = option_env!("OUT_DIR"); + _tmp_str.unwrap_or("").to_string() + }); + if out_dir.is_empty() { + env::var("LD_LIBRARY_PATH").map_or("".to_string(), |_v| { + let _tmp = _v.split(':').collect::>()[0]; + let _tmp_path = _tmp.split("target").collect::>()[0]; + _tmp_path.to_string() + }) + } else { + out_dir + } + }; + + let out_dir = devel_path(); + if !out_dir.is_empty() && out_dir.contains("build") { + let tmp_str: Vec<_> = out_dir.split("build").collect(); + self.search_path.push(tmp_str[0].to_string()); + self.preset_path.push(tmp_str[0].to_string()); + } + self.search_path.push(LIB_SYSTEM_PATH.to_string()); + self.search_path.push(RUN_SYSTEM_PATH.to_string()); + self.search_path.push(ETC_SYSTEM_PATH.to_string()); + + self.preset_path + .push(format!("{}/{}", ETC_SYSTEM_PATH, "system-preset")); + self.preset_path + .push(format!("{}/{}", LIB_SYSTEM_PATH, "system-preset")); + + self.persistent_path = ETC_SYSTEM_PATH.to_string(); + } +} + +impl Default for LookupPaths { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + + use std::env; + + use crate::logger; + + use super::LookupPaths; + #[test] + fn test_init_lookup_paths() { + logger::init_log_to_console("test_init_lookup_paths", log::LevelFilter::Trace); + let mut _lp = LookupPaths::default(); + _lp.init_lookup_paths(); + for item in _lp.search_path.iter() { + log::info!("lookup path is{:?}", item); + } + let tmp_dir = env::var("OUT_DIR"); + if tmp_dir.is_err() { + return; + } + let tmp = tmp_dir.unwrap(); + let tmp_dir_v: Vec<_> = tmp.split("build").collect(); + assert_eq!( + _lp.search_path.first().unwrap().to_string(), + tmp_dir_v[0].to_string() + ); + } +} diff --git a/libs/rustlib/src/path_util.rs b/libs/rustlib/src/path_util.rs new file mode 100644 index 0000000..4ac9de9 --- /dev/null +++ b/libs/rustlib/src/path_util.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils of the path operation +//! +use std::path::Path; + +/// return true if the path of a and b equaled. +pub fn path_equal(a: &str, b: &str) -> bool { + let p_a = Path::new(a); + let p_b = Path::new(b); + p_a == p_b +} + +#[cfg(test)] +mod tests { + use crate::path_util::path_equal; + + #[test] + fn test_path_equal() { + assert!(path_equal("/etc", "/etc")); + assert!(path_equal("//etc", "/etc")); + assert!(path_equal("/etc//", "/etc")); + assert!(!path_equal("/etc", "./etc")); + assert!(path_equal("/x/./y", "/x/y")); + assert!(path_equal("/x/././y", "/x/y/./.")); + assert!(!path_equal("/etc", "/var")); + } +} diff --git a/libs/rustlib/src/proc_cmdline.rs b/libs/rustlib/src/proc_cmdline.rs new file mode 100644 index 0000000..9628e09 --- /dev/null +++ b/libs/rustlib/src/proc_cmdline.rs @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::conf_parser; +use crate::error::*; +use nix::unistd::Pid; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::Path; + +fn cmdline_content() -> Result { + let mut file = File::open("/proc/cmdline").context(IoSnafu)?; + + let mut buf = String::new(); + match file.read_to_string(&mut buf) { + Ok(s) => s, + Err(e) => { + return Err(Error::Io { source: e }); + } + }; + Ok(buf) +} + +/// read the content from /proc/cmdline and return the value depend the key +pub fn cmdline_get_value(key: &str) -> Result, Error> { + let buf = cmdline_content()?; + + let cmdline: Vec<&str> = buf.split_whitespace().collect(); + + for cmd in cmdline.iter() { + if let Some(k_val) = cmd.split_once('=') { + if k_val.0 == key { + return Ok(Some(k_val.1.to_string())); + } + } + } + + Ok(None) +} + +/// read the content from /proc/cmdline and return specified item +///- +/// take `crashkernel=512M ro` for example, given `crashkernel` will +/// return `crashkernel=512M`, given `ro` will return `ro`, given +/// `foo` will return None. +pub fn cmdline_get_item(item: &str) -> Result, Error> { + let buf = cmdline_content()?; + let pair_item = item.to_string() + "="; + let cmdline: Vec<&str> = buf.split_whitespace().collect(); + + for cmd in cmdline.iter() { + if cmd.starts_with(&pair_item) || cmd.eq(&item) { + return Ok(Some(cmd.to_string())); + } + } + + Ok(None) +} + +/// read the content from /proc/cmdline and return the bool value depend the key +pub fn proc_cmdline_get_bool(key: &str) -> Result { + let val = cmdline_get_value(key)?; + + if val.is_none() { + return Ok(false); + } + + let r = conf_parser::parse_boolean(&val.unwrap())?; + + Ok(r) +} + +/// read /proc/PID/cmdline and return +pub fn get_process_cmdline(pid: &Pid) -> String { + let pid_str = pid.to_string(); + let cmdline_path = Path::new("/proc").join(pid_str).join("cmdline"); + let file = match File::open(cmdline_path) { + Ok(file) => file, + Err(_) => { + return String::from(""); + } + }; + let buf_reader = BufReader::new(file); + let mut cmdline_content = String::new(); + for byte in buf_reader.bytes() { + let b = match byte { + Ok(b) => b, + Err(_) => break, + }; + let b = if b != 0 { b as char } else { ' ' }; + cmdline_content += &b.to_string(); + } + cmdline_content +} diff --git a/libs/rustlib/src/process_util.rs b/libs/rustlib/src/process_util.rs new file mode 100644 index 0000000..aeb6e88 --- /dev/null +++ b/libs/rustlib/src/process_util.rs @@ -0,0 +1,242 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use crate::file_util; +use nix::errno::errno; +use nix::errno::Errno; +use nix::libc::{kill, ESRCH}; +use nix::sys::signal::Signal; +use nix::sys::wait::{waitpid, WaitPidFlag}; +use nix::unistd::Pid; +use procfs::process::Stat; +use std::collections::HashSet; +use std::fs::{read_dir, File}; +use std::path::{Path, PathBuf}; +use std::thread::sleep; +use std::time::{Duration, SystemTime}; + +/// +pub fn process_state(pid: Pid) -> Result { + if pid == Pid::from_raw(0) || pid == nix::unistd::getpid() { + return Ok('R'); + } + + let proc_file = format!("/proc/{:?}/stat", pid.as_raw()); + let stat_path = Path::new(&proc_file); + let first_line = file_util::read_first_line(stat_path)?; + let stat: Vec = first_line + .split_whitespace() + .map(|s| s.to_string()) + .collect(); + + if stat.len() < 3 { + return Err(Error::Invalid { + what: "process stat format".to_string(), + }); + } + + let p_stat: Vec = stat[3].trim().chars().collect(); + + if p_stat.is_empty() { + return Err(Error::Invalid { + what: "process state".to_string(), + }); + } + Ok(p_stat[0]) +} + +/// +pub fn alive(pid: Pid) -> bool { + if pid < Pid::from_raw(0) { + return false; + } + + if pid <= Pid::from_raw(1) { + return true; + } + + if pid == nix::unistd::getpid() { + return true; + } + + let ret = process_state(pid); + if ret.is_err() { + return false; + } + if ret.unwrap() == 'Z' { + return false; + } + + true +} + +/// +pub fn valid_pid(pid: Pid) -> bool { + if pid <= Pid::from_raw(0) { + return false; + } + + true +} + +/// +pub fn kill_all_pids(signal: i32) -> HashSet { + let mut pids: HashSet = HashSet::new(); + let proc_path = Path::new("/proc"); + let read_dir = read_dir(proc_path).unwrap(); + for entry in read_dir.flatten() { + // Skip files. + if let Ok(file_type) = entry.file_type() { + if file_type.is_file() { + continue; + } + } + let file_name = String::from(entry.file_name().to_str().unwrap()); + // Check pid directory. + if let Ok(pid_raw) = file_name.parse::() { + unsafe { + log::debug!("killing pid: {} by signal {}", pid_raw, signal); + kill(pid_raw, signal); + pids.insert(pid_raw); + } + } else { + continue; + } + } + // return PIDs we want to kill + pids +} + +/// +pub fn wait_pids(mut pids: HashSet, timeout: u64) -> HashSet { + let now = SystemTime::now(); + let until = now + Duration::from_micros(timeout); + + // remove PID1, we shouldn't wait our self and init. + pids.remove(&1); + pids.remove(&nix::unistd::getpid().into()); + + loop { + // 1. Find killed process by kernel. + while let Ok(wait_status) = waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)) { + if let Some(pid) = wait_status.pid() { + log::debug!("successfully killed pid: {} found by kernel.", pid.as_raw()); + pids.remove(&pid.as_raw()); + } else { + break; + } + } + // 2. Find killed process by sending sig: 0. + let mut removed_pids: HashSet = HashSet::new(); + for pid in &pids { + unsafe { + let res = kill(*pid, 0); + if res == 0 || errno() != ESRCH { + continue; + } + removed_pids.insert(*pid); + } + } + for pid in removed_pids { + log::debug!("successfully killed pid: {} found by ourself.", pid); + pids.remove(&pid); + } + // 3. Sleep 1s to wait pid exits. + sleep(Duration::from_secs(1)); + // 4. Wait or give up. + if pids.is_empty() { + break; + } + if SystemTime::now() >= until { + log::info!("some pids haven't been killed yet, stop waiting."); + break; + } + } + pids +} + +/// get the parent pid of the reference pid +fn get_ppid(pid: Pid) -> Result { + if pid == Pid::from_raw(0) || pid == nix::unistd::getpid() { + return Ok(nix::unistd::getppid()); + } + + let path = PathBuf::from(format!("/proc/{pid}/stat")); + + let stat = Stat::from_reader(File::open(path).context(IoSnafu)?).context(ProcSnafu)?; + + Ok(Pid::from_raw(stat.ppid)) +} + +/// return true if the pid is the child of calling process, other false. +pub fn my_child(pid: Pid) -> bool { + if pid.as_raw() <= 1 { + return false; + } + + let ppid = get_ppid(pid); + + if let Ok(p) = ppid { + return p == nix::unistd::getpid(); + } + + false +} + +/// send signal to pid, send SIGCONT if the signal is not SIGCONT or SIGKILL +pub fn kill_and_cont(pid: Pid, sig: Signal) -> Result<(), Errno> { + match nix::sys::signal::kill(pid, sig) { + Ok(_) => { + if sig != Signal::SIGCONT && sig != Signal::SIGKILL { + _ = nix::sys::signal::kill(pid, Signal::SIGCONT); + } + Ok(()) + } + Err(e) => Err(e), + } +} + +#[cfg(test)] +mod tests { + use nix::libc::kill; + use std::collections::HashSet; + use std::process::Command; + use std::thread; + + use crate::process_util::wait_pids; + #[test] + fn test_wait_pids() { + let mut pids: HashSet = HashSet::new(); + for i in 100..109 { + let str_i = i.to_string(); + let child = Command::new("/usr/bin/sleep") + .args([str_i.as_str()]) + .spawn() + .expect("Failed to fork /usr/bin/sleep"); + pids.insert(child.id() as i32); + } + + let pids_spawn = pids.clone(); + thread::spawn(move || { + for pid in pids_spawn { + unsafe { + kill(pid, 15); + } + } + }); + + let res = wait_pids(pids, 10000000); + assert_eq!(res.len(), 0); + } +} diff --git a/libs/rustlib/src/rlimit_util.rs b/libs/rustlib/src/rlimit_util.rs new file mode 100644 index 0000000..93ca8b6 --- /dev/null +++ b/libs/rustlib/src/rlimit_util.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the utils of the rlimit operation +//! + +use libc; +use std::io::Error; +use std::mem; + +/// indicate no limit +pub const INFINITY: u64 = libc::RLIM_INFINITY; + +/// get the rlimit value +pub fn getrlimit(resource: u8) -> Result<(u64, u64), Error> { + let mut rlimit = unsafe { mem::zeroed() }; + + let ret = unsafe { libc::getrlimit(resource as _, &mut rlimit) }; + + if ret == 0 { + return Ok((rlimit.rlim_cur, rlimit.rlim_max)); + } + + Err(Error::last_os_error()) +} + +/// set resource limit +pub fn setrlimit(resource: u8, soft: u64, hard: u64) -> Result<(), Error> { + let rlimit = libc::rlimit { + rlim_cur: soft as _, + rlim_max: hard as _, + }; + + let ret = unsafe { libc::setrlimit(resource as _, &rlimit) }; + if ret == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} diff --git a/libs/rustlib/src/security.rs b/libs/rustlib/src/security.rs new file mode 100644 index 0000000..bff0b4e --- /dev/null +++ b/libs/rustlib/src/security.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! some common used security functions + +#[allow(unused_imports)] +use std::{ + fs::File, + io::{BufReader, Read}, + os::{raw::c_int, unix::prelude::AsRawFd}, + path::Path, +}; + +use nix::{ + errno::Errno, + sys::{ + socket::{socket, AddressFamily, SockFlag, SockProtocol, SockType}, + stat::fstat, + }, +}; + +#[cfg(feature = "selinux")] +#[link(name = "selinux")] +extern "C" { + fn is_selinux_enabled() -> c_int; +} + +#[cfg(feature = "selinux")] +/// check if selinux is enabled +pub fn selinux_enabled() -> bool { + let res = unsafe { is_selinux_enabled() }; + res > 0 +} + +#[cfg(not(feature = "selinux"))] +/// check if selinux is enabled +pub fn selinux_enabled() -> bool { + false +} + +/// check if smack is enabled +pub fn smack_enabled() -> bool { + Path::new("/sys/fs/smackfs").exists() +} + +/// check if apparmor is enabled +pub fn apparmor_enabled() -> bool { + let mut file = match File::open("/sys/module/apparmor/parameters/enabled") { + Err(_) => { + return false; + } + Ok(v) => v, + }; + let mut buf = [0u8; 2]; + let _ = file.read_exact(&mut buf); + buf == [b'Y', b'\n'] +} + +/// check if audit is enabled +pub fn audit_enabled() -> bool { + match socket( + AddressFamily::Netlink, + SockType::Raw, + SockFlag::SOCK_CLOEXEC | SockFlag::SOCK_NONBLOCK, + SockProtocol::NetlinkAudit, + ) { + Ok(fd) => fd > 0, + Err(Errno::EAFNOSUPPORT) | Err(Errno::ENOTSUP) | Err(Errno::EPERM) => false, + Err(_) => true, + } +} + +/// check if ima is enabled +pub fn ima_enabled() -> bool { + Path::new("/sys/kernel/security/ima/").exists() +} + +/// check if the tomoyo is enabled +pub fn tomoyo_enabled() -> bool { + Path::new("/sys/kernel/security/tomoyo/version").exists() +} + +/// check if the uefi-secureboot is enabled +pub fn uefi_secureboot_enabled() -> bool { + /* mokutil check 3 files to tell if the system is secure boot enabled, + * while systemd only checks one file, we decide to copy the logic of + * systemd. The 128-bit id is a magic number, See: + * https://uefi.org/sites/default/files/resources/UEFI_Spec_Errata_Only.pdf */ + let uefi_file_path = + "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"; + let file = match File::open(uefi_file_path) { + Err(_) => { + return false; + } + Ok(v) => v, + }; + let stat = match fstat(file.as_raw_fd()) { + Err(_) => return false, + Ok(v) => v, + }; + + /* file too small or too large. */ + if stat.st_size < 4 || stat.st_size > 4 * 1024 * 1024 + 4 { + return false; + } + + let mut reader = BufReader::new(file); + /* The file should be 5 bytes, the first 4 bytes is attribute, + * the following 1 byte is value, we only care the last 1 byte + * here. */ + let mut buf = [0_u8; 5]; + if reader.read_exact(&mut buf).is_err() { + return false; + } + let value = *buf.get(4).unwrap(); + + value > 0 +} + +/// check if the tpm2 is enabled +pub fn tpm2_enabled() -> bool { + Path::new("/sys/class/tpmrm").exists() +} diff --git a/libs/rustlib/src/show_table.rs b/libs/rustlib/src/show_table.rs new file mode 100644 index 0000000..76133f8 --- /dev/null +++ b/libs/rustlib/src/show_table.rs @@ -0,0 +1,505 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! ShowTable can be used to display contents in a table format +//! ```rust,ignore +//! This is called column +//! | +//! v +//! Cell(0,0) Cell(0,1) Cell(0,2) -> This is called Line, or row +//! Cell(1,0) Cell(1,1) Cell(1,2) +//! Cell(2,0) ... ... +//! ``` +use std::fmt::Display; + +/// The alignment of one cell +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum CellAlign { + /// + Left, + /// + Right, + /// + Center, +} + +/// The color of one cell +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum CellColor { + /// + Empty, + /// + Grey, + /// + Red, + /// + Green, + /// + Yellow, + /// + Blue, + /// + Purple, +} + +impl From for String { + /// Change the CellColor to the magic string + fn from(cell_color: CellColor) -> Self { + match cell_color { + CellColor::Empty => "[37m".to_string(), + CellColor::Grey => "[30m".to_string(), + CellColor::Red => "[31m".to_string(), + CellColor::Green => "[32m".to_string(), + CellColor::Yellow => "[33m".to_string(), + CellColor::Blue => "[34m".to_string(), + CellColor::Purple => "[35m".to_string(), + } + } +} + +/// Table Cell +pub struct Cell { + color: CellColor, + align: CellAlign, + underline: bool, + left_space: bool, + right_space: bool, + /* this cell's width, don't consider cells in other lines */ + width: usize, + x_index: usize, + split_content: Vec, +} + +impl Cell { + /// Create a new cell + pub fn new(ori_content: Option<&str>, x_index: usize) -> Self { + let mut width = 0; + let mut split_content: Vec = Vec::new(); + match ori_content { + None => { + width = 0; + split_content.push(String::new()); + } + Some(ori_content) => { + for cell_line in ori_content.split('\n') { + width = std::cmp::max(width, cell_line.len()); + split_content.push(cell_line.to_string()); + } + } + } + + Cell { + color: CellColor::Empty, + underline: false, + align: CellAlign::Left, + left_space: true, + right_space: true, + width, + x_index, + split_content, + } + } + + /// Set the color of one cell + pub fn set_color(&mut self, color: CellColor) { + self.color = color; + } + + /// Set the alignment of one cell + pub fn set_align(&mut self, align: CellAlign) { + self.align = align; + } + + /// Set the cell's content underlined + pub fn set_underline(&mut self, use_underline: bool) { + self.underline = use_underline; + } + + /// Set if keep the cell's left space + pub fn set_left_space(&mut self, use_space: bool) { + self.left_space = use_space; + } + + /// Set if keep the cell's right space + pub fn set_right_space(&mut self, use_space: bool) { + self.right_space = use_space; + } + + /// Print one line of a cell out + /// + /// * i: print which line + /// + /// * width: the cell's width + /// + /// * height: the cell's height + pub fn format_cell_line(&self, i: usize, width: usize, height: usize) -> String { + let mut res = String::new(); + if i >= self.split_content.len() { + res += &" ".repeat(width); + } else { + res += &self.split_content[i]; + } + if self.align == CellAlign::Left && width > res.len() { + res += &" ".repeat(width - res.len()); + } else if self.align == CellAlign::Right && width > res.len() { + res = " ".repeat(width - res.len()) + &res; + } else if self.align == CellAlign::Center && width > res.len() { + let left_size = (width - res.len()) / 2; + res = " ".repeat(left_size) + &res; + res += &" ".repeat(width - res.len()); + } + if self.left_space { + res = " ".to_string() + &res; + } + if self.right_space { + res += " "; + } + let mut prefix = String::new(); + if self.color != CellColor::Empty { + prefix = "\x1b".to_string() + &String::from(self.color); + } + if i == height - 1 && self.underline { + prefix += "\x1b[4m"; + } + if !prefix.is_empty() { + res = prefix + &res + "\x1b[0m"; + } + res + } +} + +/// Table Line +pub struct Line { + /// Height of this total line + height: usize, + /// Width of this line, don't consider other lines + per_widths: Vec, + /// Cells of this line + cells: Vec, +} + +impl Line { + /// Create an empty line + pub fn empty() -> Self { + Self { + per_widths: Vec::new(), + height: 0, + cells: Vec::new(), + } + } + /// Create a new line by a Vec<&str> + pub fn new(ori_contents: Vec<&str>) -> Self { + let mut per_widths = Vec::new(); + let mut height = 0; + let mut cells = Vec::new(); + for (x_index, ori_content) in ori_contents.into_iter().enumerate() { + let cell = Cell::new(Some(ori_content), x_index); + height = std::cmp::max(cell.split_content.len(), height); + per_widths.push(cell.width); + cells.push(cell); + } + Self { + height, + per_widths, + cells, + } + } +} + +/// ShowTable +pub struct ShowTable { + /* width of the table, don't consider left_space or right_space. */ + global_widths: Vec, + lines: Vec, +} + +impl ShowTable { + /// Create a new empty ShowTable + pub fn new() -> Self { + Self { + global_widths: Vec::new(), + lines: Vec::new(), + } + } + + /// Add a ShowTableLine to ShowTable + pub fn add_show_table_item(&mut self, show_table_line: &impl ShowTableLine) { + let line = show_table_line.to_vec(); + self.add_line(line); + } + + /// Add a single line to ShowTable + pub fn add_line(&mut self, ori_contents: Vec<&str>) { + let line = Line::new(ori_contents); + /* The table is empty. */ + if self.global_widths.is_empty() { + for v in &line.per_widths { + self.global_widths.push(*v); + } + } else { + if line.per_widths.len() != self.global_widths.len() { + log::error!("Can not add this line to ShowTable, their lengths are different."); + return; + } + for i in 0..line.per_widths.len() { + self.global_widths[i] = std::cmp::max(self.global_widths[i], line.per_widths[i]); + } + } + self.lines.push(line); + } + /// Set all cells's alignment to left + pub fn set_all_cell_align_left(&mut self) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].align = CellAlign::Left; + } + } + } + /// Set all cell's alignment to right + pub fn set_all_cell_align_right(&mut self) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].align = CellAlign::Right; + } + } + } + /// Set all cells' alignment to center + pub fn set_all_cell_align_center(&mut self) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].align = CellAlign::Center; + } + } + } + /// Set a certain row's alignment + pub fn set_one_row_align(&mut self, i: usize, align: CellAlign) { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].align = align; + } + } + /// Set current row's alignment + /// + /// This is useful when one add a new line, and wants to change its format immediately. + pub fn set_current_row_align(&mut self, align: CellAlign) { + let total_line = self.lines.len(); + if total_line < 1 { + log::info!("Failed to set current row's align."); + return; + } + for j in 0..self.lines[total_line - 1].cells.len() { + self.lines[total_line - 1].cells[j].align = align; + } + } + /// Set a certain column's alignment + pub fn set_one_col_align(&mut self, j: usize, align: CellAlign) { + for i in 0..self.lines.len() { + self.lines[i].cells[j].align = align; + } + } + /// Set a cell's alignment + pub fn set_one_cell_align(&mut self, i: usize, j: usize, align: CellAlign) { + self.lines[i].cells[j].align = align; + } + /// Set all cells' split space + pub fn set_all_cell_space(&mut self, left_space: bool, right_space: bool) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].left_space = left_space; + self.lines[i].cells[j].right_space = right_space; + } + } + } + /// Set one row's split space + pub fn set_one_row_space(&mut self, i: usize, left_space: bool, right_space: bool) { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].left_space = left_space; + self.lines[i].cells[j].right_space = right_space; + } + } + /// Set one column's split space + pub fn set_one_col_space(&mut self, j: usize, left_space: bool, right_space: bool) { + for i in 0..self.lines.len() { + self.lines[i].cells[j].left_space = left_space; + self.lines[i].cells[j].right_space = right_space; + } + } + /// Set one cell's split space + pub fn set_one_cell_space(&mut self, i: usize, j: usize, left_space: bool, right_space: bool) { + self.lines[i].cells[j].left_space = left_space; + self.lines[i].cells[j].right_space = right_space; + } + /// Set all cells' underline + pub fn set_all_cell_underline(&mut self, use_underline: bool) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].underline = use_underline; + } + } + } + /// Set one row's underline + pub fn set_one_row_underline(&mut self, i: usize, use_underline: bool) { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].underline = use_underline; + } + } + /// Set the current row's underline + pub fn set_current_row_underline(&mut self, use_underline: bool) { + let total_line = self.lines.len(); + if total_line < 1 { + log::info!("Failed to set current row's underline."); + return; + } + for j in 0..self.lines[total_line - 1].cells.len() { + self.lines[total_line - 1].cells[j].underline = use_underline; + } + } + /// Set one column's underline + pub fn set_one_col_underline(&mut self, j: usize, use_underline: bool) { + for i in 0..self.lines.len() { + self.lines[i].cells[j].underline = use_underline; + } + } + /// Set one cell's underline + pub fn set_one_cell_underline(&mut self, i: usize, j: usize, use_underline: bool) { + self.lines[i].cells[j].underline = use_underline; + } + /// Set all cells' color + pub fn set_all_cell_color(&mut self, color: CellColor) { + for i in 0..self.lines.len() { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].color = color; + } + } + } + /// Set one row's color + pub fn set_one_row_color(&mut self, i: usize, color: CellColor) { + for j in 0..self.lines[i].cells.len() { + self.lines[i].cells[j].color = color; + } + } + /// Set current row's color + pub fn set_current_row_color(&mut self, color: CellColor) { + let total_line = self.lines.len(); + if total_line < 1 { + log::info!("Failed to set current row's color."); + return; + } + for j in 0..self.lines[total_line - 1].cells.len() { + self.lines[total_line - 1].cells[j].color = color; + } + } + /// Set one column's color + pub fn set_one_col_color(&mut self, j: usize, color: CellColor) { + for i in 0..self.lines.len() { + self.lines[i].cells[j].color = color; + } + } + /// Set one cell's color + pub fn set_one_cell_color(&mut self, i: usize, j: usize, color: CellColor) { + self.lines[i].cells[j].color = color; + } +} + +impl Default for ShowTable { + fn default() -> Self { + Self::new() + } +} + +impl Display for ShowTable { + /// Print the whole ShowTable out + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut res = String::new(); + /* print one line */ + for line in &self.lines { + /* The cell can be multi-cell_line, print one by one */ + for i in 0..line.height { + for cell in &line.cells { + res += &cell.format_cell_line(i, self.global_widths[cell.x_index], line.height); + } + res += "\n"; + } + } + write!(f, "{}", res.trim_end()) + } +} + +/// Struct can implement this trait to display in ShowTable +pub trait ShowTableLine { + /// change the struct to the string vector + fn to_vec(&self) -> Vec<&str> { + todo!() + } +} + +mod tests { + use super::ShowTableLine; + + struct TestItem { + value1: String, + value2: String, + value3: String, + } + + impl ShowTableLine for TestItem { + fn to_vec(&self) -> Vec<&str> { + vec![&self.value1, &self.value2, &self.value3] + } + } + + #[test] + fn run_test() { + use super::ShowTable; + let mut table1 = ShowTable::new(); + table1.add_line(vec!["AAA", "BBBB", "CCCCCCCCCC"]); + table1.add_line(vec!["12345", "123", "123"]); + assert_eq!( + table1.to_string(), + " AAA BBBB CCCCCCCCCC \n 12345 123 123" + ); + table1.set_all_cell_align_right(); + assert_eq!( + table1.to_string(), + " AAA BBBB CCCCCCCCCC \n 12345 123 123" + ); + table1.set_all_cell_align_left(); + table1.set_one_row_align(0, crate::show_table::CellAlign::Right); + assert_eq!( + table1.to_string(), + " AAA BBBB CCCCCCCCCC \n 12345 123 123" + ); + table1.set_all_cell_align_right(); + table1.set_one_col_align(0, crate::show_table::CellAlign::Left); + assert_eq!( + table1.to_string(), + " AAA BBBB CCCCCCCCCC \n 12345 123 123" + ); + + let test_item1 = TestItem { + value1: "AAA".to_string(), + value2: "BBBB".to_string(), + value3: "CCCCCCCCCC".to_string(), + }; + let test_item2 = TestItem { + value1: "12345".to_string(), + value2: "123".to_string(), + value3: "123".to_string(), + }; + let mut table2 = ShowTable::new(); + table2.add_show_table_item(&test_item1); + table2.add_show_table_item(&test_item2); + assert_eq!( + table2.to_string(), + " AAA BBBB CCCCCCCCCC \n 12345 123 123" + ); + } +} diff --git a/libs/rustlib/src/socket_util.rs b/libs/rustlib/src/socket_util.rs new file mode 100644 index 0000000..c2fd2f8 --- /dev/null +++ b/libs/rustlib/src/socket_util.rs @@ -0,0 +1,90 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 crate::error::*; +use nix::{ + errno::Errno, + sys::socket::{self, sockopt, AddressFamily}, +}; +use std::{os::unix::prelude::RawFd, path::Path}; + +/// +pub fn ipv6_is_supported() -> bool { + let inet6 = Path::new("/proc/net/if_inet6"); + + if inet6.exists() { + return true; + } + + false +} + +/// +pub fn set_pkginfo(fd: RawFd, family: AddressFamily, v: bool) -> Result<()> { + match family { + socket::AddressFamily::Inet => { + socket::setsockopt(fd as RawFd, sockopt::Ipv4PacketInfo, &v).context(NixSnafu) + } + socket::AddressFamily::Inet6 => { + socket::setsockopt(fd as RawFd, sockopt::Ipv6RecvPacketInfo, &v).context(NixSnafu) + } + _ => Err(Error::Nix { + source: Errno::EAFNOSUPPORT, + }), + } +} + +/// +pub fn set_pass_cred(fd: RawFd, v: bool) -> Result<()> { + socket::setsockopt(fd, sockopt::PassCred, &v).context(NixSnafu) +} + +/// +pub fn set_receive_buffer(fd: RawFd, v: usize) -> Result<()> { + socket::setsockopt(fd, sockopt::RcvBuf, &v).context(NixSnafu) +} + +/// +pub fn set_send_buffer(fd: RawFd, v: usize) -> Result<()> { + socket::setsockopt(fd, sockopt::SndBuf, &v).context(NixSnafu) +} + +/// Require specific privileges to ignore the kernel limit +pub fn set_receive_buffer_force(fd: RawFd, v: usize) -> Result<()> { + socket::setsockopt(fd, sockopt::RcvBufForce, &v).context(NixSnafu) +} + +/// Set keepalive properties +pub fn set_keepalive_state(fd: RawFd, v: bool) -> Result<()> { + socket::setsockopt(fd, sockopt::KeepAlive, &v).context(NixSnafu) +} + +/// Set the interval between the last data packet sent and the first keepalive probe +pub fn set_keepalive_timesec(fd: RawFd, v: u32) -> Result<()> { + socket::setsockopt(fd, sockopt::TcpKeepIdle, &v).context(NixSnafu) +} + +/// Set the interval between subsequential keepalive probes +pub fn set_keepalive_intervalsec(fd: RawFd, v: u32) -> Result<()> { + socket::setsockopt(fd, sockopt::TcpKeepInterval, &v).context(NixSnafu) +} + +/// Set the number of unacknowledged probes to send +pub fn set_keepalive_probes(fd: RawFd, v: u32) -> Result<()> { + socket::setsockopt(fd, sockopt::TcpKeepCount, &v).context(NixSnafu) +} + +/// Set Broadcast state +pub fn set_broadcast_state(fd: RawFd, v: bool) -> Result<()> { + socket::setsockopt(fd, sockopt::Broadcast, &v).context(NixSnafu) +} diff --git a/libs/rustlib/src/special.rs b/libs/rustlib/src/special.rs new file mode 100644 index 0000000..0266af3 --- /dev/null +++ b/libs/rustlib/src/special.rs @@ -0,0 +1,39 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! the special name of the system unit + +/// default startup target +pub const DEFAULT_TARGET: &str = "default.target"; +/// the shutdown target +pub const SHUTDOWN_TARGET: &str = "shutdown.target"; +/// the socketc target +pub const SOCKETS_TARGET: &str = "sockets.target"; + +/// early boot targets +pub const SYSINIT_TARGET: &str = "sysinit.target"; +/// the basic start target +pub const BASIC_TARGET: &str = "basic.target"; + +/// Special user boot targets */ +pub const MULTI_USER_TARGET: &str = "multi-user.target"; + +/// the init scope +pub const INIT_SCOPE: &str = "init.scope"; +/// sysmaster service slice +pub const SYSMASTER_SLICE: &str = "system.slice"; + +/// the unit store sysmaster itself +pub const CGROUP_SYSMASTER: &str = "sysmaster"; + +/// the default running time directory of sysmaster +pub const EXEC_RUNTIME_PREFIX: &str = "/run"; diff --git a/libs/rustlib/src/stat_util.rs b/libs/rustlib/src/stat_util.rs new file mode 100644 index 0000000..9d4bb0f --- /dev/null +++ b/libs/rustlib/src/stat_util.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! utility for stat +//! + +use super::{Error, Result}; +use nix::{ + errno::Errno, + sys::statfs::{self, FsType}, +}; + +#[cfg(any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android" +))] +use nix::sys::statfs::PROC_SUPER_MAGIC; + +/// check whether a path is specific file system type +pub fn path_is_fs_type(path: &str, magic: FsType) -> Result { + let s = statfs::statfs(path).map_err(|e| Error::Nix { source: e })?; + + Ok(s.filesystem_type() == magic) +} + +/// check whether /proc is mounted +pub fn proc_mounted() -> Result { + #[cfg(any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android" + ))] + match path_is_fs_type("/proc/", PROC_SUPER_MAGIC) { + Ok(r) => return Ok(r), + Err(e) => match e { + Error::Nix { + source: Errno::ENOENT, + } => {} + _ => return Err(e), + }, + } + + Ok(false) +} diff --git a/libs/rustlib/src/string.rs b/libs/rustlib/src/string.rs new file mode 100644 index 0000000..cac3189 --- /dev/null +++ b/libs/rustlib/src/string.rs @@ -0,0 +1,22 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! Common used string functions + +/// Add "\n" to s. +/// This can be used when generating a multi-line string. +/// Use this function before you write a new line. +pub fn new_line_break(s: &mut String) { + if !s.is_empty() { + *s += "\n"; + } +} diff --git a/libs/rustlib/src/time_util.rs b/libs/rustlib/src/time_util.rs new file mode 100644 index 0000000..45a12af --- /dev/null +++ b/libs/rustlib/src/time_util.rs @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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::time::SystemTime; + +const USEC_INFINITY: u128 = u128::MAX; + +/// usec per sec +pub const USEC_PER_SEC: u64 = 1000000; + +/// +pub fn timespec_load(systime: SystemTime) -> u128 { + match systime.duration_since(SystemTime::UNIX_EPOCH) { + Ok(d) => d.as_micros(), + Err(_) => USEC_INFINITY, + } +} diff --git a/libs/rustlib/src/user_group_util.rs b/libs/rustlib/src/user_group_util.rs new file mode 100644 index 0000000..bd9e4b2 --- /dev/null +++ b/libs/rustlib/src/user_group_util.rs @@ -0,0 +1,171 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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. + +//! Common used functions to parse user and group + +use crate::error::*; +use nix::libc::uid_t; +use nix::unistd::{Gid, Group, Uid, User}; + +/// Parse a string as UID +pub fn parse_uid(uid_str: &String) -> Result { + if uid_str.is_empty() { + return Err(Error::Invalid { + what: "UID is empty".to_string(), + }); + } + + if uid_str.eq("0") { + // This shouldn't fail. + return Ok(User::from_uid(Uid::from_raw(0)).unwrap().unwrap()); + } + + let mut first = true; + for c in uid_str.bytes() { + // uid must only contains 0-9 + if !first && c.is_ascii_digit() { + continue; + } + // uid must starts with 1-9 + if first && (b'1'..=b'9').contains(&c) { + first = false; + continue; + } + return Err(Error::Invalid { + what: "UID must only contains 0-9 and shouldn't starts with 0".to_string(), + }); + } + + let uid = match uid_str.parse::() { + Err(e) => { + return Err(e.into()); + } + Ok(v) => v, + }; + + let user = match User::from_uid(Uid::from_raw(uid)) { + Err(e) => { + return Err(Error::Nix { source: e }); + } + Ok(v) => v, + }; + + match user { + None => Err(Error::Invalid { + what: "No matched UID".to_string(), + }), + Some(v) => Ok(v), + } +} + +/// Parse a string as UID +pub fn parse_gid(gid_str: &String) -> Result { + // Same logic as parse_uid() + if gid_str.is_empty() { + return Err(Error::Invalid { + what: "GID is empty".to_string(), + }); + } + + if gid_str.eq("0") { + return Ok(Group::from_gid(Gid::from_raw(0)).unwrap().unwrap()); + } + + let mut first = true; + for c in gid_str.bytes() { + if !first && c.is_ascii_digit() { + continue; + } + if first && (b'1'..=b'9').contains(&c) { + first = false; + continue; + } + return Err(Error::Invalid { + what: "GID must only contains 0-9 and shouldn't starts with 0".to_string(), + }); + } + + let gid = match gid_str.parse::() { + Err(e) => { + return Err(e.into()); + } + Ok(v) => v, + }; + + let group = match Group::from_gid(Gid::from_raw(gid)) { + Err(e) => { + return Err(Error::Nix { source: e }); + } + Ok(v) => v, + }; + + match group { + None => Err(Error::Invalid { + what: "No matched GID".to_string(), + }), + Some(v) => Ok(v), + } +} + +/// Parse a string as Username +pub fn parse_name(name_str: &String) -> Result { + if name_str.is_empty() { + return Err(Error::Invalid { + what: "Username is empty".to_string(), + }); + } + + let user = match User::from_name(name_str).context(NixSnafu) { + Err(e) => { + return Err(e); + } + Ok(v) => v, + }; + match user { + None => Err(Error::Invalid { + what: "No matched username".to_string(), + }), + Some(v) => Ok(v), + } +} + +/// check if the user id is within the system user range +pub fn uid_is_system(uid: Uid) -> bool { + const SYSTEM_UID_MAX: uid_t = 999; + uid.as_raw() <= SYSTEM_UID_MAX +} + +/// get user creds +pub fn get_user_creds(user: &String) -> Result { + if let Ok(u) = parse_uid(user) { + return Ok(u); + } + if let Ok(Some(u)) = User::from_name(user) { + return Ok(u); + } + Err(Error::Invalid { + what: "invalid user name".to_string(), + }) +} + +/// get group creds +pub fn get_group_creds(group: &String) -> Result { + if let Ok(g) = parse_gid(group) { + return Ok(g); + } + if let Ok(Some(g)) = Group::from_name(group) { + return Ok(g); + } + Err(Error::Invalid { + what: "invalid group name".to_string(), + }) +} diff --git a/libs/rustlib/src/virtualize.rs b/libs/rustlib/src/virtualize.rs new file mode 100644 index 0000000..a3d32d7 --- /dev/null +++ b/libs/rustlib/src/virtualize.rs @@ -0,0 +1,74 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster 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 nix::unistd::AccessFlags; +use std::env; + +/// Virtualization system +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Virtualization { + /// not virtualization + None = 0, + /// docker virtualization + Docker, + /// lxc virtualization + Lxc, + /// podman virtualization + Podman, + /// podman virtualization + Containerd, + /// not supported virtualization + NotSupported, +} + +impl From for Virtualization { + fn from(action: String) -> Self { + match action.as_ref() { + "podman" => Virtualization::Podman, + "lxc" => Virtualization::Lxc, + "docker" => Virtualization::Docker, + "containerd" => Virtualization::Containerd, + _ => Virtualization::NotSupported, + } + } +} + +/// if running in container return true, others return false +pub fn detect_container() -> Virtualization { + if let Ok(v) = env::var("container") { + if v.is_empty() { + return Virtualization::None; + } + return Virtualization::from(v); + } + + detect_container_files() +} + +fn detect_container_files() -> Virtualization { + match nix::unistd::access("/run/.containerenv", AccessFlags::F_OK) { + Ok(_) => return Virtualization::Podman, + Err(e) => { + log::debug!("access /run/.cantainerenv error: {}", e); + } + } + + match nix::unistd::access("/.dockerenv", AccessFlags::F_OK) { + Ok(_) => return Virtualization::Docker, + Err(e) => { + log::debug!("access /.dockerenv error: {}", e); + } + } + + Virtualization::None +} -- Gitee