diff --git a/Cargo.lock b/Cargo.lock index 1310ad7be11819f6ed58f81aa1db6f6ee633690f..60c6b30e773b21b4ef8c15e5a6963d962aeda619 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1132,6 +1132,7 @@ dependencies = [ "nix 0.24.3", "once_cell", "serde", + "unit_parser", ] [[package]] diff --git a/core/coms/mount/Cargo.toml b/core/coms/mount/Cargo.toml index 037966ff731ecd5512a4925333ed08c89793827e..80f336a9c849f2f73e5cda7d763743a8d14c64b4 100644 --- a/core/coms/mount/Cargo.toml +++ b/core/coms/mount/Cargo.toml @@ -18,6 +18,7 @@ event = { path = "../../../libs/event" } log = { path = "../../../libs/log" } macros = { path = "../../../libs/macros" } constants = { path = "../../../libs/constants", optional = true } +unit_parser = { path = "../../../libs/unit_parser" } epoll = "=4.3.1" libc = { version = "0.2.*", default-features = false } @@ -31,6 +32,7 @@ once_cell = { version = "=1.8.0", default-features = false } serde = { version = "1.0.130", default-features = false } [features] -default = ["noplugin"] +default = ["noplugin", "linux"] noplugin = [] +linux = [] plugin = ["constants"] diff --git a/core/coms/mount/src/comm.rs b/core/coms/mount/src/comm.rs index d58af2271215d3b889a2c1f0361e31c57a8de291..d0068d869f3b90ee6a17f67f0baba8a25aa7b6b2 100644 --- a/core/coms/mount/src/comm.rs +++ b/core/coms/mount/src/comm.rs @@ -72,7 +72,7 @@ impl MountUnitComm { self.umcomm.rentry() } - pub(super) fn _um(&self) -> Rc { + pub(super) fn um(&self) -> Rc { self.umcomm.um() } } diff --git a/core/coms/mount/src/config.rs b/core/coms/mount/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bad5dde07d6e8a6c15583fd3610c5eac06418f2 --- /dev/null +++ b/core/coms/mount/src/config.rs @@ -0,0 +1,128 @@ +// 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::{cell::RefCell, path::PathBuf, rc::Rc}; + +use unit_parser::prelude::UnitConfig; + +use crate::{comm::MountUnitComm, rentry::SectionMount}; + +#[derive(UnitConfig, Default)] +#[allow(non_snake_case)] +pub(super) struct MountConfigData { + pub Mount: SectionMount, +} + +#[allow(unused)] +pub(super) struct MountConfig { + // associated objects + comm: Rc, + + // owned objects + data: Rc>, +} + +pub(super) struct MountParameters { + pub what: String, + pub options: String, + pub fstype: String, +} + +impl MountConfig { + pub(super) fn new(commr: &Rc) -> Self { + MountConfig { + comm: Rc::clone(commr), + data: Rc::new(RefCell::new(MountConfigData::default())), + } + } + + pub(super) fn load(&self, paths: Vec, name: &str, _update: bool) { + log::debug!("Loading {} config from: {:?}", name, paths); + let mount_config = match MountConfigData::load_config(paths, name) { + Ok(v) => v, + Err(e) => { + log::error!("Invalid Configuration: {}", e); + return; + } + }; + *self.data.borrow_mut() = mount_config; + + // if update { + // self.db_update(); + // } + } + + pub(super) fn config_data(&self) -> Rc> { + self.data.clone() + } + + pub(super) fn mount_where(&self) -> String { + self.data.borrow().Mount.Where.clone() + } + + pub(super) fn set_mount_where(&self, mount_where: &str) { + (*self.data.borrow_mut()).Mount.Group = mount_where.to_string(); + } + + pub(super) fn mount_what(&self) -> String { + self.data.borrow().Mount.What.clone() + } + + pub(super) fn mount_type(&self) -> String { + self.data.borrow().Mount.Type.clone() + } + + pub(super) fn mount_options(&self) -> String { + self.data.borrow().Mount.Options.clone() + } + + pub(super) fn directory_mode(&self) -> u32 { + self.data.borrow().Mount.DirectoryMode + } + + #[allow(unused)] + pub(super) fn force_unmount(&self) -> bool { + self.data.borrow().Mount.ForceUnmount + } + + pub(super) fn mount_parameters(&self) -> MountParameters { + MountParameters { + what: self.mount_what(), + options: self.mount_options(), + fstype: self.mount_type(), + } + } + + pub(super) fn update_mount_parameters(&self, what: &str, options: &str, fstype: &str) { + (*self.data.borrow_mut()).Mount.What = what.to_string(); + (*self.data.borrow_mut()).Mount.Options = options.to_string(); + (*self.data.borrow_mut()).Mount.Type = fstype.to_string(); + } +} + +pub(super) fn mount_is_bind(mount_parameters: &MountParameters) -> bool { + // This is a simplified version. + for v in mount_parameters.options.split(',') { + let option = v.trim(); + if option == "bind" || option == "rbind" { + return true; + } + } + if mount_parameters.options.contains("bind|rbind") { + return true; + } + if mount_parameters.fstype == "bind" || mount_parameters.fstype == "rbind" { + return true; + } + false +} diff --git a/core/coms/mount/src/lib.rs b/core/coms/mount/src/lib.rs index 529c82da01675c10138898066650935f9289f99a..4b6524b1f7508826228d43045165ea61e1516884 100644 --- a/core/coms/mount/src/lib.rs +++ b/core/coms/mount/src/lib.rs @@ -29,7 +29,9 @@ pub use {manager::__um_obj_create, unit::__subunit_create_with_params}; // dependency: mount_base -> mount_rentry -> mount_comm -> {mount_mng -> mount_unit} -> mount_manager mod base; mod comm; +mod config; mod manager; mod mng; mod rentry; +mod spawn; mod unit; diff --git a/core/coms/mount/src/manager.rs b/core/coms/mount/src/manager.rs index 5bc660e3bcbac3aea70a317aa0b3e99000136c07..5a0345bfbe05801e2adcfb3e89167dbd6602675b 100644 --- a/core/coms/mount/src/manager.rs +++ b/core/coms/mount/src/manager.rs @@ -12,17 +12,17 @@ #[cfg(feature = "plugin")] use crate::base::PLUGIN_NAME; +use basic::fs_util::is_path; #[cfg(feature = "plugin")] use constants::LOG_FILE_PATH; use super::comm::MountUmComm; use super::rentry::{MountRe, MountReFrame}; -use basic::mount_util::MountInfoParser; +use basic::mount_util::{mount_point_to_unit_name, MountInfoParser}; use core::error::*; use core::rel::{ReStation, ReliLastFrame, Reliability}; -use core::unit::{UmIf, UnitActiveState, UnitManagerObj, UnitMngUtil, UnitType}; +use core::unit::{unit_name_is_valid, UmIf, UnitManagerObj, UnitMngUtil, UnitNameFlags, UnitType}; use event::{EventState, EventType, Events, Source}; -use std::collections::HashSet; use std::fs::File; use std::io::Read; use std::os::unix::io::{AsRawFd, RawFd}; @@ -326,16 +326,62 @@ impl MountMonitorData { } } - pub fn dispatch_mountinfo(&self) -> Result<()> { - // First mark all active mount point we have as dead. - let mut dead_mount_set: HashSet = HashSet::new(); - let unit_type = Some(UnitType::UnitMount); - for unit in self.comm.um().units_get_all(unit_type).iter() { - if self.comm.um().current_active_state(unit) == UnitActiveState::Active { - dead_mount_set.insert(String::from(unit)); - } + pub fn setup_new_mount( + &self, + unit_name: &str, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) -> Result<()> { + self.comm + .um() + .setup_new_mount(unit_name, what, mount_where, options, fstype); + Ok(()) + } + + pub fn setup_existing_mount( + &self, + unit_name: &str, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) -> Result<()> { + self.comm + .um() + .setup_existing_mount(unit_name, what, mount_where, options, fstype); + Ok(()) + } + + pub fn setup_mount( + &self, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) -> Result<()> { + if fstype == "autofs" { + return Ok(()); + } + if !is_path(mount_where) { + return Ok(()); } + let unit_name = mount_point_to_unit_name(mount_where); + if !unit_name_is_valid(&unit_name, UnitNameFlags::PLAIN) { + return Err(Error::InvalidName { + what: "mount point can't convert to unit name".to_string(), + }); + } + if self.comm.um().load_unit_success(&unit_name) { + self.setup_existing_mount(&unit_name, what, mount_where, options, fstype)?; + } else { + self.setup_new_mount(&unit_name, what, mount_where, options, fstype)?; + } + Ok(()) + } + pub fn load_mountinfo(&self) -> Result<()> { // Then start mount point we don't know. let mut mountinfo_content = String::new(); File::open("/proc/self/mountinfo") @@ -344,42 +390,26 @@ impl MountMonitorData { .unwrap(); let parser = MountInfoParser::new(mountinfo_content); for mount in parser { - // pop - // We don't process autofs for now, because it is not - // .mount but .automount in systemd. - if mount.fstype == "autofs" { - continue; - } - let unit_name = mount_point_to_unit_name(&mount.mount_point); - if dead_mount_set.contains(unit_name.as_str()) { - dead_mount_set.remove(unit_name.as_str()); - } else if self.comm.um().load_unit_success(unit_name.as_str()) { - // record + action - self.comm.reli().set_last_unit(&unit_name); - let start_ok = self.comm.um().unit_start_directly(&unit_name).is_ok(); - self.comm.reli().clear_last_unit(); - - if start_ok { - log::debug!("{} change to mounted.", unit_name); - } else { - log::error!("Failed to start {}", unit_name); - } + if let Err(e) = self.setup_mount( + &mount.mount_source, + &mount.mount_point, + &mount.mount_options, + &mount.fstype, + ) { + log::error!("Failed to setup mount {}: {}", mount.mount_point, e); } } + Ok(()) + } - // Finally stop mount point in dead_mount_set. - for unit_name in dead_mount_set.into_iter() { - // pop - // record + action - self.comm.reli().set_last_unit(&unit_name); - let ret = self.comm.um().unit_stop(&unit_name, false); - self.comm.reli().clear_last_unit(); - - if ret.is_ok() { - log::debug!("{} change to dead.", unit_name); - } else { - log::error!("Failed to stop {}.", unit_name); - } + pub fn dispatch_mountinfo(&self) -> Result<()> { + let unit_type = Some(UnitType::UnitMount); + for unit in self.comm.um().units_get_all(unit_type).iter() { + self.comm.um().update_mount_state(unit, "dead"); + } + self.load_mountinfo()?; + for unit in self.comm.um().units_get_all(unit_type).iter() { + self.comm.um().update_mount_state(unit, "mounted"); } Ok(()) } @@ -391,14 +421,6 @@ fn drain_out(epfd: i32) { while epoll::wait(epfd, 0, &mut me_events).unwrap() > 0 {} } -fn mount_point_to_unit_name(mount_point: &str) -> String { - let mut res = String::from(mount_point).replace('/', "-") + ".mount"; - if res != "-.mount" { - res = String::from(&res[1..]) - } - res -} - impl UnitMngUtil for MountManager { fn attach_um(&self, um: Rc) { self.comm.attach_um(um); diff --git a/core/coms/mount/src/mng.rs b/core/coms/mount/src/mng.rs index cfb832acc5ad6d98f8ae9035c5dbeeb866547273..1bbdabeecbdeebbd5603ee7002cbe2a2b53b8fa3 100644 --- a/core/coms/mount/src/mng.rs +++ b/core/coms/mount/src/mng.rs @@ -11,11 +11,22 @@ // See the Mulan PSL v2 for more details. //! The core logic of the mount subclass +use basic::fs_util::{directory_is_empty, mkdir_p_label}; +use basic::mount_util::filter_options; +use basic::{MOUNT_BIN, UMOUNT_BIN}; +use nix::sys::wait::WaitStatus; + +use crate::config::{mount_is_bind, MountConfig}; +use crate::rentry::MountResult; +use crate::spawn::MountSpawn; + use super::comm::MountUnitComm; use super::rentry::MountState; use core::error::*; +use core::exec::{ExecCommand, ExecContext}; use core::rel::ReStation; use core::unit::{UnitActiveState, UnitNotifyFlags}; +use std::path::Path; use std::{cell::RefCell, rc::Rc}; impl MountState { @@ -23,6 +34,7 @@ impl MountState { match *self { MountState::Dead => UnitActiveState::InActive, MountState::Mounted => UnitActiveState::Active, + _ => UnitActiveState::InActive, } } } @@ -30,6 +42,14 @@ impl MountState { pub(super) struct MountMng { comm: Rc, state: RefCell, + + config: Rc, + control_command: RefCell>, + spawn: Rc, + + result: RefCell, + reload_result: RefCell, + find_in_mountinfo: RefCell, } impl ReStation for MountMng { @@ -50,17 +70,90 @@ impl ReStation for MountMng { } impl MountMng { - pub(super) fn new(_comm: &Rc) -> Self { + pub(super) fn new( + commr: &Rc, + configr: &Rc, + exec_ctx: &Rc, + ) -> Self { MountMng { - comm: Rc::clone(_comm), + comm: Rc::clone(commr), state: RefCell::new(MountState::Dead), + config: Rc::clone(configr), + control_command: RefCell::new(None), + spawn: Rc::new(MountSpawn::new(commr, exec_ctx)), + result: RefCell::new(MountResult::Success), + reload_result: RefCell::new(MountResult::Success), + find_in_mountinfo: RefCell::new(false), } } - // process doesn't support manually mount/umount like systemd. - // We only monitor the state of mountpoint. + pub(super) fn enter_mounting(&self) { + let mount_config = self.config.config_data(); + let mount_config = mount_config.borrow(); + + let mount_where = Path::new(&mount_config.Mount.Where); + let mount_what = Path::new(&mount_config.Mount.What); + let directory_mode = self.config.directory_mode(); + + let _ = mkdir_p_label(mount_where, directory_mode); + if !mount_where.exists() || !mount_where.is_dir() { + log::error!( + "Failed to create the mount directory: {}", + mount_config.Mount.Where + ); + return; + } + if !directory_is_empty(mount_where) { + log::warn!( + "The mount directory {} is not empty.", + mount_config.Mount.Where + ); + } - pub(super) fn enter_dead(&self, notify: bool) { + let mount_parameters = self.config.mount_parameters(); + if mount_is_bind(&mount_parameters) { + if let Err(e) = mkdir_p_label(mount_what, directory_mode) { + log::error!( + "Failed to create mount source {}: {}", + mount_config.Mount.What, + e + ); + } + } + let filtered_options = + filter_options(&mount_parameters.options, vec!["nofail", "noauto", "auto"]); + + let mut mount_command = ExecCommand::empty(); + if let Err(e) = mount_command.set_path(MOUNT_BIN) { + log::error!("Failed to set mount command: {}", e); + return; + } + mount_command.append_many_argv(vec![&mount_parameters.what, &mount_config.Mount.Where]); + if !mount_parameters.fstype.is_empty() { + mount_command.append_many_argv(vec!["-t", &mount_parameters.fstype]); + } + if !filtered_options.is_empty() { + mount_command.append_many_argv(vec!["-o", &filtered_options]); + } + *self.control_command.borrow_mut() = Some(mount_command.clone()); + if let Err(e) = self.spawn.spawn_cmd(&mount_command) { + log::error!( + "Failed to mount {} to {}: {}", + &mount_config.Mount.What, + &mount_config.Mount.Where, + e + ); + return; + } + + self.set_state(MountState::Mounting, true); + } + + pub(super) fn enter_signal(&self, _state: MountState, _res: MountResult) {} + + pub(super) fn enter_dead_or_mounted(&self, _res: MountResult) {} + + pub(super) fn enter_dead(&self, _res: MountResult, notify: bool) { self.set_state(MountState::Dead, notify); } @@ -68,16 +161,93 @@ impl MountMng { self.set_state(MountState::Mounted, notify); } + pub(super) fn enter_unmounting(&self) { + // retry_umount + let mut umount_command = ExecCommand::empty(); + if let Err(e) = umount_command.set_path(UMOUNT_BIN) { + log::error!("Failed to set umount command: {}", e); + } + let mount_where = self.config.mount_where(); + umount_command.append_many_argv(vec![&mount_where, "-c"]); + *self.control_command.borrow_mut() = Some(umount_command.clone()); + if let Err(e) = self.spawn.spawn_cmd(&umount_command) { + log::error!("Failed to umount {}: {}", mount_where, e); + return; + } + self.set_state(MountState::Unmounting, true); + } + + #[allow(unused)] + pub(super) fn enter_remounting(&self) {} + + #[allow(unused)] + pub(super) fn dispatch_timer(&self) {} + pub(super) fn start_check(&self) -> Result { let ret = self.comm.owner().map_or(false, |u| u.test_start_limit()); if !ret { - self.enter_dead(true); + self.enter_dead(MountResult::Success, true); return Err(Error::UnitActionECanceled); } Ok(false) } + pub(super) fn start_action(&self) -> Result<()> { + if [ + MountState::Unmounting, + MountState::UnmountingSigterm, + MountState::UnmountingSigkill, + ] + .contains(&self.state()) + { + return Err(Error::UnitActionEAgain); + } + + if [MountState::Mounting, MountState::MountingDone].contains(&self.state()) { + return Ok(()); + } + + self.enter_mounting(); + Ok(()) + } + + pub(super) fn stop_action(&self) -> Result { + let state = self.state(); + if [ + MountState::Unmounting, + MountState::UnmountingSigkill, + MountState::UnmountingSigterm, + ] + .contains(&state) + { + return Ok(0); + } + if [ + MountState::Mounting, + MountState::MountingDone, + MountState::Remounting, + ] + .contains(&state) + { + self.enter_signal(MountState::UnmountingSigterm, MountResult::Success); + return Ok(0); + } + if state == MountState::RemountingSigterm { + self.set_state(MountState::UnmountingSigterm, true); + return Ok(0); + } + if state == MountState::RemountingSigKill { + self.set_state(MountState::UnmountingSigkill, true); + return Ok(0); + } + if state == MountState::Mounted { + self.enter_unmounting(); + return Ok(1); + } + Ok(0) + } + pub fn get_state(&self) -> String { let state = *self.state.borrow(); state.to_string() @@ -126,36 +296,151 @@ impl MountMng { pub(super) fn mount_state_to_unit_state(&self) -> UnitActiveState { self.state().mount_state_to_unit_state() } -} -#[cfg(test)] -mod tests { - use super::MountMng; - use super::MountState; - use super::MountUnitComm; - use std::rc::Rc; + fn result(&self) -> MountResult { + *self.result.borrow() + } + + fn set_result(&self, r: MountResult) { + *self.result.borrow_mut() = r + } + + fn set_reload_result(&self, r: MountResult) { + if *self.reload_result.borrow() != MountResult::Success { + return; + } + *self.reload_result.borrow_mut() = r + } - #[test] - fn test_mount_set_state() { - let _comm = Rc::new(MountUnitComm::new()); - let tm = MountMng::new(&_comm); - tm.set_state(MountState::Mounted, false); - assert_eq!(tm.state(), MountState::Mounted) + pub fn find_in_mountinfo(&self) -> bool { + *self.find_in_mountinfo.borrow() + } + + pub fn set_find_in_mountinfo(&self, find: bool) { + *self.find_in_mountinfo.borrow_mut() = find + } +} + +impl MountMng { + pub(super) fn sigchld_event(&self, wait_status: WaitStatus) { + self.do_sigchld_event(wait_status); + self.db_update(); } - #[test] - fn test_mount_enter_dead() { - let _comm = Rc::new(MountUnitComm::new()); - let tm = MountMng::new(&_comm); - tm.enter_dead(false); - assert_eq!(tm.state(), MountState::Dead) + fn do_sigchld_event(&self, wait_status: WaitStatus) { + log::debug!("Got a mount process sigchld, status: {:?}", wait_status); + let mut f = self.sigchld_result(wait_status); + let state = self.state(); + + if [ + MountState::Remounting, + MountState::RemountingSigKill, + MountState::RemountingSigterm, + ] + .contains(&state) + { + self.set_reload_result(f); + } else if self.result() == MountResult::Success { + self.set_result(f); + } + if self.control_command.borrow().is_some() { + *self.control_command.borrow_mut() = None; + } + + // mount process has exited, but the mount point is not mounted + if state == MountState::Mounting { + if f == MountResult::Success { + log::warn!( + "Mount process of {} has exited, but there is no mount.", + self.comm.get_owner_id() + ); + f = MountResult::FailureProtocol; + } + self.enter_dead(f, true); + return; + } + + // we have seen this mountpoint in /p/s/mountinfo + if state == MountState::MountingDone { + self.enter_mounted(true); + return; + } + + if [ + MountState::Remounting, + MountState::RemountingSigKill, + MountState::RemountingSigterm, + ] + .contains(&state) + { + self.enter_dead_or_mounted(MountResult::Success); + return; + } + + if state == MountState::Unmounting { + // umount process has exited, but we can still see the mountpoint in /p/s/mountinfo + if f == MountResult::Success && self.find_in_mountinfo() { + self.enter_mounted(true); + } else { + self.enter_dead_or_mounted(f); + } + return; + } + + if [MountState::UnmountingSigkill, MountState::UnmountingSigterm].contains(&state) { + self.enter_dead_or_mounted(f); + } } - #[test] - fn test_mount_enter_mounted() { - let _comm = Rc::new(MountUnitComm::new()); - let tm = MountMng::new(&_comm); - tm.enter_mounted(false); - assert_eq!(tm.state(), MountState::Mounted) + fn sigchld_result(&self, wait_status: WaitStatus) -> MountResult { + match wait_status { + WaitStatus::Exited(_, status) => { + if status == 0 { + MountResult::Success + } else { + MountResult::FailureExitCode + } + } + WaitStatus::Signaled(_pid, _sig, core_dump) => { + if core_dump { + MountResult::FailureCoreDump + } else { + MountResult::FailureSignal + } + } + _ => unreachable!(), + } } } + +// #[cfg(test)] +// mod tests { +// use super::MountMng; +// use super::MountState; +// use super::MountUnitComm; +// use std::rc::Rc; + +// #[test] +// fn test_mount_set_state() { +// let _comm = Rc::new(MountUnitComm::new()); +// let tm = MountMng::new(&_comm); +// tm.set_state(MountState::Mounted, false); +// assert_eq!(tm.state(), MountState::Mounted) +// } + +// #[test] +// fn test_mount_enter_dead() { +// let _comm = Rc::new(MountUnitComm::new()); +// let tm = MountMng::new(&_comm); +// tm.enter_dead(false); +// assert_eq!(tm.state(), MountState::Dead) +// } + +// #[test] +// fn test_mount_enter_mounted() { +// let _comm = Rc::new(MountUnitComm::new()); +// let tm = MountMng::new(&_comm); +// tm.enter_mounted(false); +// assert_eq!(tm.state(), MountState::Mounted) +// } +// } diff --git a/core/coms/mount/src/rentry.rs b/core/coms/mount/src/rentry.rs index 3408c4c68b742798caf7d6388fcbac249e80fb95..ef19514fe7429e82f39ce32d6cfb2d6502e8ceee 100644 --- a/core/coms/mount/src/rentry.rs +++ b/core/coms/mount/src/rentry.rs @@ -10,10 +10,13 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use core::rel::{ReDb, ReDbRwTxn, ReDbTable, ReliSwitch, Reliability}; -use macros::EnumDisplay; +use core::{ + exec::{PreserveMode, Rlimit, RuntimeDirectory, StateDirectory, WorkingDirectory}, + rel::{ReDb, ReDbRwTxn, ReDbTable, ReliSwitch, Reliability}, +}; +use macros::{EnumDisplay, UnitSection}; use serde::{Deserialize, Serialize}; -use std::rc::Rc; +use std::{collections::HashMap, path::PathBuf, rc::Rc}; const RELI_DB_HMOUNT_MNG: &str = "mntmng"; const RELI_DB_HMOUNTM_FRAME: &str = "mntm-frame"; @@ -23,7 +26,29 @@ const RELI_LAST_KEY: u32 = 0; // singleton #[derive(PartialEq, Eq, Debug, Copy, Clone, Serialize, Deserialize, EnumDisplay)] pub(super) enum MountState { Dead, + Mounting, + MountingDone, Mounted, + Remounting, + Unmounting, + RemountingSigterm, + RemountingSigKill, + UnmountingSigterm, + UnmountingSigkill, + Failed, + // Cleaning currently not used +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)] +pub(super) enum MountResult { + Success, + FailureResources, + FailureTimeout, + FailureExitCode, + FailureSignal, + FailureCoreDump, + FailureStartLimitHit, + FailureProtocol, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -137,3 +162,46 @@ impl ReDbTable for MountReDb { self.0.switch_buffer(switch); } } + +#[derive(UnitSection, Default)] +#[allow(non_snake_case)] +pub struct SectionMount { + #[entry(default = String::new())] + pub What: String, + #[entry(default = String::new())] + pub Where: String, + #[entry(default = String::new())] + pub Type: String, + #[entry(default = String::new())] + pub Options: String, + #[entry(default = 0o755)] + pub DirectoryMode: u32, + #[entry(default = false)] + pub ForceUnmount: bool, + + // Exec + #[entry(default = String::new())] + pub User: String, + #[entry(default = String::new())] + pub Group: String, + #[entry(default = String::from("0022"))] + pub UMask: String, + #[entry(parser = basic::fs_util::parse_pathbuf)] + pub RootDirectory: Option, + #[entry(default = WorkingDirectory::default(), parser = core::exec::parse_working_directory)] + pub WorkingDirectory: WorkingDirectory, + #[entry(default = StateDirectory::default(), parser = core::exec::parse_state_directory)] + pub StateDirectory: StateDirectory, + #[entry(default = RuntimeDirectory::default(), parser = core::exec::parse_runtime_directory)] + pub RuntimeDirectory: RuntimeDirectory, + #[entry(default = PreserveMode::No)] + pub RuntimeDirectoryPreserve: PreserveMode, + pub LimitCORE: Option, + pub LimitNOFILE: Option, + pub LimitNPROC: Option, + #[entry(parser = core::exec::parse_environment)] + pub Environment: Option>, + #[entry(append)] + pub EnvironmentFile: Vec, + pub SELinuxContext: Option, +} diff --git a/core/coms/mount/src/spawn.rs b/core/coms/mount/src/spawn.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a536536e2cdcde8f9b14e9f765a462db868df95 --- /dev/null +++ b/core/coms/mount/src/spawn.rs @@ -0,0 +1,55 @@ +// 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 core::error::*; +use core::exec::{ExecCommand, ExecContext, ExecParameters}; +use std::rc::Rc; + +use nix::unistd::Pid; + +use crate::comm::MountUnitComm; + +pub(crate) struct MountSpawn { + comm: Rc, + exec_ctx: Rc, +} + +impl MountSpawn { + pub(super) fn new(comm: &Rc, exec_ctx: &Rc) -> MountSpawn { + MountSpawn { + comm: comm.clone(), + exec_ctx: exec_ctx.clone(), + } + } + + pub(super) fn spawn_cmd(&self, cmdline: &ExecCommand) -> Result { + let mut params = ExecParameters::new(); + + if let Some(unit) = self.comm.owner() { + let um = self.comm.um(); + unit.prepare_exec()?; + match um.exec_spawn(&unit.id(), cmdline, &mut params, self.exec_ctx.clone()) { + Ok(pid) => { + um.child_watch_pid(&unit.id(), pid); + Ok(pid) + } + Err(e) => { + log::error!("Failed to spawn the mount command of {}: {}", unit.id(), e); + Err("spawn mount command error".to_string().into()) + } + } + } else { + Err("spawn mount command error".to_string().into()) + } + } +} diff --git a/core/coms/mount/src/unit.rs b/core/coms/mount/src/unit.rs index 704c957156bd4b4cf4ca2b61ac294402a22eddb7..17768c6d98385aa1ffdddc92825545ca33de430b 100644 --- a/core/coms/mount/src/unit.rs +++ b/core/coms/mount/src/unit.rs @@ -13,9 +13,14 @@ //! mount unit is entry of mount type of unit,need impl //! UnitObj,UnitMngUtil, UnitSubClass trait +use crate::config::MountConfig; +use crate::rentry::MountResult; + use super::comm::MountUnitComm; use super::mng::MountMng; +use basic::mount_util::mount_point_to_unit_name; use core::error::*; +use core::exec::ExecContext; use core::rel::{ReStation, Reliability}; use core::unit::{SubUnit, UmIf, UnitActiveState, UnitBase, UnitMngUtil}; use nix::sys::wait::WaitStatus; @@ -25,6 +30,8 @@ use std::rc::Rc; struct MountUnit { comm: Rc, mng: Rc, + config: Rc, + exec_ctx: Rc, } impl ReStation for MountUnit { @@ -53,11 +60,81 @@ impl ReStation for MountUnit { impl MountUnit { fn new(_um: Rc) -> MountUnit { - let _comm = Rc::new(MountUnitComm::new()); + let comm = Rc::new(MountUnitComm::new()); + let config = Rc::new(MountConfig::new(&comm)); + let exec_ctx = Rc::new(ExecContext::new()); + let mng = Rc::new(MountMng::new(&comm, &config, &exec_ctx)); MountUnit { - comm: Rc::clone(&_comm), - mng: Rc::new(MountMng::new(&_comm)), + comm, + mng, + config, + exec_ctx, + } + } + + fn parse(&self) -> Result<()> { + let cfg_data = self.config.config_data(); + self.exec_ctx + .insert_envs_files(cfg_data.borrow().Mount.EnvironmentFile.clone()); + + if let Some(rlimit) = cfg_data.borrow().Mount.LimitCORE { + self.exec_ctx.insert_rlimit(libc::RLIMIT_CORE as u8, rlimit); + } + + if let Some(rlimit) = cfg_data.borrow().Mount.LimitNOFILE { + self.exec_ctx + .insert_rlimit(libc::RLIMIT_NOFILE as u8, rlimit); + } + + if let Some(rlimit) = cfg_data.borrow().Mount.LimitNPROC { + self.exec_ctx + .insert_rlimit(libc::RLIMIT_NPROC as u8, rlimit); + } + + self.exec_ctx + .set_root_directory(cfg_data.borrow().Mount.RootDirectory.clone()); + self.exec_ctx + .set_working_directory(cfg_data.borrow().Mount.WorkingDirectory.clone()); + self.exec_ctx + .set_runtime_directory(cfg_data.borrow().Mount.RuntimeDirectory.clone()); + self.exec_ctx + .set_state_directory(cfg_data.borrow().Mount.StateDirectory.clone()); + + self.exec_ctx + .set_selinux_context(cfg_data.borrow().Mount.SELinuxContext.clone()); + + #[cfg(feature = "linux")] + if let Err(e) = self.exec_ctx.set_user(&cfg_data.borrow().Mount.User) { + log::error!("Failed to set user: {}", e); + return Err(e); + } + + #[cfg(feature = "linux")] + if let Err(e) = self.exec_ctx.set_group(&cfg_data.borrow().Mount.Group) { + log::error!("Failed to set group: {}", e); + return Err(e); + } + + if let Err(e) = self.exec_ctx.set_umask(&cfg_data.borrow().Mount.UMask) { + log::error!("Failed to set umask: {}", e); + return Err(e); + } + + Ok(()) + } + + fn mount_verify(&self) -> Result<()> { + let mount_where = self.config.mount_where(); + if !mount_point_to_unit_name(&mount_where).eq(&self.comm.get_owner_id()) { + log::error!( + "Failed to load unit {}: unit name doesn't match Where", + self.comm.get_owner_id() + ); + return Err(Error::ConfigureError { + msg: "unit name doesn't match Where".to_string(), + }); } + Ok(()) } } @@ -66,11 +143,11 @@ impl SubUnit for MountUnit { self } - fn load(&self, _paths: Vec) -> Result<()> { - if let Some(u) = self.comm.owner() { - u.set_ignore_on_isolate(true) - } - Ok(()) + fn load(&self, paths: Vec) -> Result<()> { + let unit_name = self.comm.get_owner_id(); + self.config.load(paths, &unit_name, true); + self.parse()?; + self.mount_verify() } fn current_active_state(&self) -> UnitActiveState { @@ -93,19 +170,22 @@ impl SubUnit for MountUnit { fn dump(&self) {} fn start(&self) -> Result<()> { + log::info!("Mounting {}", self.comm.get_owner_id()); let started = self.mng.start_check()?; if started { - log::debug!("mount already in starting, just return immediately"); + log::debug!("{} is being mounted, skipping.", self.comm.get_owner_id()); return Ok(()); } + self.mng.start_action()?; self.mng.enter_mounted(true); Ok(()) } fn stop(&self, _force: bool) -> Result<()> { - self.mng.enter_dead(true); + self.mng.stop_action()?; + self.mng.enter_dead(MountResult::Success, true); Ok(()) } @@ -113,9 +193,40 @@ impl SubUnit for MountUnit { fn release_resources(&self) {} - fn sigchld_events(&self, _wait_status: WaitStatus) {} + fn sigchld_events(&self, wait_status: WaitStatus) { + self.mng.sigchld_event(wait_status); + } fn reset_failed(&self) {} + + fn setup_existing_mount(&self, what: &str, mount_where: &str, options: &str, fstype: &str) { + if self.config.mount_where().is_empty() { + self.config.set_mount_where(mount_where); + } + self.config.update_mount_parameters(what, options, fstype); + self.mng.set_find_in_mountinfo(true); + } + + fn setup_new_mount(&self, what: &str, mount_where: &str, options: &str, fstype: &str) { + self.config.set_mount_where(mount_where); + self.config.update_mount_parameters(what, options, fstype); + self.mng.set_find_in_mountinfo(true); + } + + fn update_mount_state(&self, state: &str) { + match state { + "dead" => { + self.mng.set_find_in_mountinfo(false); + self.mng.enter_dead(MountResult::Success, true); + } + "mounted" => { + if self.mng.find_in_mountinfo() { + self.mng.enter_mounted(true); + } + } + _ => {} + } + } } impl UnitMngUtil for MountUnit { diff --git a/core/coms/service/src/mng.rs b/core/coms/service/src/mng.rs index cf545f4b61851b4b65c7779870d39411208900d0..eaf5469447b67e426a136f868e3d8b0025114d7b 100644 --- a/core/coms/service/src/mng.rs +++ b/core/coms/service/src/mng.rs @@ -19,11 +19,11 @@ use super::rentry::{ NotifyState, ServiceCommand, ServiceRestart, ServiceResult, ServiceState, ServiceType, }; use super::spawn::ServiceSpawn; -use crate::rentry::{ExitStatus, NotifyAccess, PreserveMode}; +use crate::rentry::{ExitStatus, NotifyAccess}; use basic::{do_entry_log, fd_util, IN_SET}; use basic::{fs_util, process}; use core::error::*; -use core::exec::{ExecCommand, ExecContext, ExecFlag, ExecFlags}; +use core::exec::{ExecCommand, ExecContext, ExecFlag, ExecFlags, PreserveMode}; use core::rel::ReStation; use core::unit::{KillOperation, UnitActiveState, UnitNotifyFlags}; use event::{EventState, EventType, Events, Source}; diff --git a/core/coms/service/src/rentry.rs b/core/coms/service/src/rentry.rs index 8195cdc52760cd9078aa1c1d389ce36b03e4c43a..798a2724156f0a2cc2ca56cb309e406399556de6 100644 --- a/core/coms/service/src/rentry.rs +++ b/core/coms/service/src/rentry.rs @@ -13,9 +13,8 @@ #![allow(non_snake_case)] use crate::monitor::ServiceMonitor; -use basic::fs_util::{ - parse_absolute_path, path_is_abosolute, path_length_is_valid, path_name_is_safe, path_simplify, -}; +use basic::fs_util::{path_is_abosolute, path_length_is_valid, path_name_is_safe, path_simplify}; +use core::exec::PreserveMode; use macros::{EnumDisplay, UnitSection}; use nix::sys::signal::Signal; use nix::sys::wait::WaitStatus; @@ -202,40 +201,7 @@ impl UnitEntry for ExitStatusSet { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum PreserveMode { - No, - Yes, - Restart, -} - -impl Default for PreserveMode { - fn default() -> Self { - Self::No - } -} - -impl UnitEntry for PreserveMode { - type Error = core::error::Error; - - fn parse_from_str>(input: S) -> std::result::Result { - let res = match input.as_ref() { - "no" => PreserveMode::No, - "yes" => PreserveMode::Yes, - "restart" => PreserveMode::Restart, - _ => { - log::error!( - "Failed to parse RuntimeDirectoryPreserve: {}, assuming no", - input.as_ref() - ); - PreserveMode::No - } - }; - Ok(res) - } -} - -fn deserialize_pidfile(s: &str) -> Result { +fn parse_pidfile(s: &str) -> Result { if !path_name_is_safe(s) { return Err(core::error::Error::ConfigureError { msg: "PIDFile contains invalid character".to_string(), @@ -264,129 +230,7 @@ fn deserialize_pidfile(s: &str) -> Result { } } -fn deserialize_pathbuf(s: &str) -> Result { - let path = parse_absolute_path(s).map_err(|_| core::error::Error::ConfigureError { - msg: "Invalid PathBuf".to_string(), - })?; - Ok(PathBuf::from(path)) -} - -fn parse_working_directory(s: &str) -> Result { - if s.is_empty() { - return Ok(WorkingDirectory::new(None, true)); - } - - let mut miss_ok = false; - if s.starts_with('-') { - miss_ok = true; - } - - let mut s: String = s.trim_start_matches('-').to_string(); - - if s == *"~".to_string() { - s = std::env::var("HOME").map_err(|_| basic::Error::Invalid { - what: "can't get HOME environment".to_string(), - })?; - } - - Ok(WorkingDirectory::new(Some(PathBuf::from(&s)), miss_ok)) -} - -#[cfg(test)] -mod test { - use std::path::PathBuf; - - use crate::rentry::parse_working_directory; - - #[test] - fn test_parse_working_directory() { - assert_eq!( - parse_working_directory("/root").unwrap().directory(), - Some(PathBuf::from("/root")) - ); - assert_eq!( - parse_working_directory("-/root/foooooooobarrrrrr") - .unwrap() - .directory(), - Some(PathBuf::from("/root/foooooooobarrrrrr")) - ); - assert_eq!( - parse_working_directory("--------------/usr/lib") - .unwrap() - .directory(), - Some(PathBuf::from("/usr/lib")) - ); - assert_eq!( - parse_working_directory("~").unwrap().directory(), - Some(PathBuf::from(std::env::var("HOME").unwrap())) - ); - assert_eq!(parse_working_directory("").unwrap().directory(), None); - } -} - -fn is_valid_exec_directory(s: &str) -> bool { - if !path_name_is_safe(s) { - return false; - } - if !path_length_is_valid(s) { - return false; - } - if path_is_abosolute(s) { - return false; - } - true -} - -fn deserialize_runtime_directory(s: &str) -> Result { - let mut res = RuntimeDirectory::default(); - for d in s.split_terminator(';') { - if !is_valid_exec_directory(d) { - return Err(Error::ConfigureError { - msg: "invalid runtime directory".to_string(), - }); - } - - let path = match path_simplify(d) { - None => { - return Err(Error::ConfigureError { - msg: "invalid runtime directory".to_string(), - }); - } - Some(v) => v, - }; - - res.add_directory(Path::new("/run").join(path)); - } - - Ok(res) -} - -fn deserialize_state_directory(s: &str) -> Result { - /* Similar with RuntimeDirectory */ - let mut res = StateDirectory::default(); - for d in s.split_terminator(';') { - if !is_valid_exec_directory(d) { - return Err(Error::ConfigureError { - msg: "not valid exec directory".to_string(), - }); - } - - let path = match path_simplify(d) { - None => { - return Err(Error::ConfigureError { - msg: "not valid exec directory".to_string(), - }); - } - Some(v) => v, - }; - - res.add_directory(Path::new("/var/lib").join(path)); - } - - Ok(res) -} - -fn deserialize_timeout(s: &str) -> Result { +fn parse_timeout(s: &str) -> Result { let timeout = s.parse::().unwrap(); if timeout == 0 { return Ok(u64::MAX); @@ -397,182 +241,77 @@ fn deserialize_timeout(s: &str) -> Result { Ok(timeout * USEC_PER_SEC) } -fn parse_environment(s: &str) -> Result> { - #[derive(PartialEq, Clone, Copy)] - enum ParseState { - Init, - Key, - Value, - Quotes, - BackSlash, - WaitSpace, - Invalid, - } - - let mut state = ParseState::Init; - let mut state_before_back_slash = ParseState::Value; - let mut key = String::new(); - let mut value = String::new(); - let mut res: HashMap = HashMap::new(); - for c in s.chars() { - match state { - ParseState::Init => { - if !key.is_empty() && !value.is_empty() { - res.insert(key, value); - } - key = String::new(); - value = String::new(); - if c.is_ascii_alphanumeric() || c == '_' { - key += &c.to_string(); - state = ParseState::Key; - } else if c != ' ' { - state = ParseState::Invalid; - break; - } - } - ParseState::Key => { - if c.is_ascii_alphanumeric() || c == '_' { - key += &c.to_string(); - } else if c == '=' { - state = ParseState::Value; - } else { - /* F-O=foo */ - state = ParseState::Invalid; - break; - } - } - ParseState::Value => { - /* FOO="foo bar" */ - if c == '\"' { - state = ParseState::Quotes; - continue; - } - /* FOO==\"foo */ - if c == '\\' { - state = ParseState::BackSlash; - state_before_back_slash = ParseState::Value; - continue; - } - if c != ' ' { - value += &c.to_string(); - continue; - } - state = ParseState::Init; - } - ParseState::BackSlash => { - /* FOO=\"foo or FOO="\"foo bar" */ - value += &c.to_string(); - state = state_before_back_slash; - } - ParseState::Quotes => { - /* We have got the right ", there must a space after. */ - if c == '\"' { - state = ParseState::WaitSpace; - continue; - } - if c == '\\' { - state = ParseState::BackSlash; - state_before_back_slash = ParseState::Quotes; - continue; - } - value += &c.to_string(); - } - ParseState::WaitSpace => { - if c != ' ' { - /* FOO="foo bar"x */ - state = ParseState::Invalid; - break; - } else { - state = ParseState::Init; - } - } - ParseState::Invalid => { - break; - } - } - } - if state == ParseState::Invalid { - log::warn!("Found invalid Environment, breaking"); - return Ok(res); - } - if !key.is_empty() - && !value.is_empty() - && [ParseState::Init, ParseState::WaitSpace, ParseState::Value].contains(&state) - { - res.insert(key, value); - } - Ok(res) -} - #[derive(UnitSection, Serialize, Deserialize, Debug, Default, Clone)] pub struct SectionService { #[entry(default=ServiceType::Simple)] pub Type: ServiceType, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStart: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStartPre: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStartPost: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStop: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStopPost: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecReload: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecCondition: Vec, #[entry(append)] pub Sockets: Vec, #[entry(default = 0)] pub WatchdogSec: u64, - #[entry(parser = deserialize_pidfile)] + #[entry(parser = parse_pidfile)] pub PIDFile: Option, #[entry(default = false)] pub RemainAfterExit: bool, pub NotifyAccess: Option, #[entry(default = false)] pub NonBlocking: bool, - #[entry(parser = parse_environment)] - pub Environment: Option>, #[entry(default = KillMode::ControlGroup)] pub KillMode: KillMode, - pub SELinuxContext: Option, - #[entry(parser = deserialize_pathbuf)] - pub RootDirectory: Option, - #[entry(default = WorkingDirectory::default(), parser = parse_working_directory)] - pub WorkingDirectory: WorkingDirectory, - #[entry(default = StateDirectory::default(), parser = deserialize_state_directory)] - pub StateDirectory: StateDirectory, - #[entry(default = RuntimeDirectory::default(), parser = deserialize_runtime_directory)] - pub RuntimeDirectory: RuntimeDirectory, - #[entry(default = PreserveMode::No)] - pub RuntimeDirectoryPreserve: PreserveMode, - #[entry(default = String::new())] - pub User: String, - #[entry(default = String::new())] - pub Group: String, - #[entry(default = String::from("0022"))] - pub UMask: String, #[entry(default = ServiceRestart::No)] pub Restart: ServiceRestart, #[entry(default = ExitStatusSet::default())] pub RestartPreventExitStatus: ExitStatusSet, #[entry(default = 1)] pub RestartSec: u64, - #[entry(append)] - pub EnvironmentFile: Vec, #[entry(default = String::from("SIGTERM"))] pub KillSignal: String, - #[entry(default = 10000000, parser = deserialize_timeout)] + #[entry(default = 10000000, parser = parse_timeout)] pub TimeoutSec: u64, - #[entry(default = 10000000, parser = deserialize_timeout)] + #[entry(default = 10000000, parser = parse_timeout)] pub TimeoutStartSec: u64, - #[entry(default = 10000000, parser = deserialize_timeout)] + #[entry(default = 10000000, parser = parse_timeout)] pub TimeoutStopSec: u64, + + // Exec + #[entry(default = String::new())] + pub User: String, + #[entry(default = String::new())] + pub Group: String, + #[entry(default = String::from("0022"))] + pub UMask: String, + #[entry(parser = basic::fs_util::parse_pathbuf)] + pub RootDirectory: Option, + #[entry(default = WorkingDirectory::default(), parser = core::exec::parse_working_directory)] + pub WorkingDirectory: WorkingDirectory, + #[entry(default = StateDirectory::default(), parser = core::exec::parse_state_directory)] + pub StateDirectory: StateDirectory, + #[entry(default = RuntimeDirectory::default(), parser = core::exec::parse_runtime_directory)] + pub RuntimeDirectory: RuntimeDirectory, + #[entry(default = PreserveMode::No)] + pub RuntimeDirectoryPreserve: PreserveMode, pub LimitCORE: Option, pub LimitNOFILE: Option, pub LimitNPROC: Option, + #[entry(parser = core::exec::parse_environment)] + pub Environment: Option>, + #[entry(append)] + pub EnvironmentFile: Vec, + pub SELinuxContext: Option, } impl SectionService { diff --git a/core/coms/socket/src/rentry.rs b/core/coms/socket/src/rentry.rs index 50b0b0e5234d329be3e45fa955aee04590ac8dbf..6755bbb4fd22ddea67e925a99c4d3323e7674feb 100644 --- a/core/coms/socket/src/rentry.rs +++ b/core/coms/socket/src/rentry.rs @@ -29,7 +29,7 @@ const RELI_DB_HSOCKET_MNG: &str = "sockmng"; const RELI_DB_HSOCKETM_FRAME: &str = "sockm-frame"; const RELI_LAST_KEY: u32 = 0; // singleton -fn deserialize_pathbuf_vec(s: &str) -> Result, core::error::Error> { +fn parse_pathbuf_vec(s: &str) -> Result, core::error::Error> { let mut res = Vec::new(); for v in s.split_ascii_whitespace() { let path = basic::fs_util::parse_absolute_path(v).map_err(|_| { @@ -49,15 +49,15 @@ fn deserialize_netlink_vec(s: &str) -> Result, core::error::Error> { #[derive(UnitSection, Default, Clone, Debug, Serialize, Deserialize)] #[allow(dead_code)] pub(super) struct SectionSocket { - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStartPre: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStartChown: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStartPost: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStopPre: Vec, - #[entry(append, parser = core::exec::deserialize_exec_command)] + #[entry(append, parser = core::exec::parse_exec_command)] pub ExecStopPost: Vec, #[entry(append)] @@ -89,7 +89,7 @@ pub(super) struct SectionSocket { pub Broadcast: Option, #[entry(default = false)] pub RemoveOnStop: bool, - #[entry(append, parser = deserialize_pathbuf_vec)] + #[entry(append, parser = parse_pathbuf_vec)] pub Symlinks: Vec, pub PassSecurity: Option, #[entry(default = 0o666)] diff --git a/core/libcore/src/error.rs b/core/libcore/src/error.rs index 197d9ccb64481591650609b1655beb43e58c40f2..28a47d32891e8d49a3ad045b8a9d90833b61f347 100644 --- a/core/libcore/src/error.rs +++ b/core/libcore/src/error.rs @@ -103,6 +103,11 @@ pub enum Error { source: Arc, }, + #[snafu(display("Invalid Name: {}", what))] + InvalidName { + what: String, + }, + #[snafu(display("ConvertError"))] ConvertToSysmaster, @@ -190,6 +195,7 @@ impl From for nix::Error { Error::NotFound { what: _ } => nix::Error::ENOENT, Error::Other { msg: _ } => nix::Error::EIO, Error::Shared { source: _ } => nix::Error::EIO, + Error::InvalidName { what: _ } => nix::Error::EINVAL, Error::ConvertToSysmaster => nix::Error::EIO, Error::Input => nix::Error::EIO, Error::Conflict => nix::Error::EBADR, diff --git a/core/libcore/src/exec/base.rs b/core/libcore/src/exec/base.rs index af64c2e02b3a1cd9f240230caec5383866588ec7..134a8c047141b673e89671cd07a7dc3c7015d104 100644 --- a/core/libcore/src/exec/base.rs +++ b/core/libcore/src/exec/base.rs @@ -11,6 +11,7 @@ // See the Mulan PSL v2 for more details. use crate::error::*; +use basic::fs_util::{path_is_abosolute, path_length_is_valid, path_name_is_safe, path_simplify}; use basic::rlimit; use bitflags::bitflags; use libc::EPERM; @@ -91,7 +92,7 @@ fn parse_rlimit(limit: &str) -> Result { } impl FromStr for Rlimit { - type Err = Error; + type Err = crate::error::Error; fn from_str(s: &str) -> std::result::Result { let value: Vec<_> = s.trim().split_terminator(':').collect(); let soft: u64; @@ -118,6 +119,56 @@ impl FromStr for Rlimit { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// +pub enum PreserveMode { + /// + No, + /// + Yes, + /// + Restart, +} + +impl Default for PreserveMode { + fn default() -> Self { + Self::No + } +} + +impl UnitEntry for PreserveMode { + type Error = Error; + + fn parse_from_str>(input: S) -> std::result::Result { + let res = match input.as_ref() { + "no" => PreserveMode::No, + "yes" => PreserveMode::Yes, + "restart" => PreserveMode::Restart, + _ => { + log::error!( + "Failed to parse RuntimeDirectoryPreserve: {}, assuming no", + input.as_ref() + ); + PreserveMode::No + } + }; + Ok(res) + } +} + +fn is_valid_exec_directory(s: &str) -> bool { + if !path_name_is_safe(s) { + return false; + } + if !path_length_is_valid(s) { + return false; + } + if path_is_abosolute(s) { + return false; + } + true +} + /// WorkingDirectory of ExecContext #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct WorkingDirectory { @@ -125,6 +176,28 @@ pub struct WorkingDirectory { miss_ok: bool, } +/// +pub fn parse_working_directory(s: &str) -> Result { + if s.is_empty() { + return Ok(WorkingDirectory::new(None, true)); + } + + let mut miss_ok = false; + if s.starts_with('-') { + miss_ok = true; + } + + let mut s: String = s.trim_start_matches('-').to_string(); + + if s == *"~".to_string() { + s = std::env::var("HOME").map_err(|_| basic::Error::Invalid { + what: "can't get HOME environment".to_string(), + })?; + } + + Ok(WorkingDirectory::new(Some(PathBuf::from(&s)), miss_ok)) +} + impl WorkingDirectory { /// pub fn new(directory: Option, miss_ok: bool) -> Self { @@ -148,6 +221,31 @@ pub struct RuntimeDirectory { directory: Vec, } +/// +pub fn parse_runtime_directory(s: &str) -> Result { + let mut res = RuntimeDirectory::default(); + for d in s.split_terminator(';') { + if !is_valid_exec_directory(d) { + return Err(Error::ConfigureError { + msg: "invalid runtime directory".to_string(), + }); + } + + let path = match path_simplify(d) { + None => { + return Err(Error::ConfigureError { + msg: "invalid runtime directory".to_string(), + }); + } + Some(v) => v, + }; + + res.add_directory(Path::new("/run").join(path)); + } + + Ok(res) +} + impl RuntimeDirectory { /// pub fn add_directory(&mut self, directory: PathBuf) { @@ -166,6 +264,32 @@ pub struct StateDirectory { directory: Vec, } +/// +pub fn parse_state_directory(s: &str) -> Result { + /* Similar with RuntimeDirectory */ + let mut res = StateDirectory::default(); + for d in s.split_terminator(';') { + if !is_valid_exec_directory(d) { + return Err(Error::ConfigureError { + msg: "not valid exec directory".to_string(), + }); + } + + let path = match path_simplify(d) { + None => { + return Err(Error::ConfigureError { + msg: "not valid exec directory".to_string(), + }); + } + Some(v) => v, + }; + + res.add_directory(Path::new("/var/lib").join(path)); + } + + Ok(res) +} + impl StateDirectory { /// pub fn add_directory(&mut self, directory: PathBuf) { @@ -201,6 +325,114 @@ impl Default for ExecContext { } } +/// +pub fn parse_environment(s: &str) -> Result> { + #[derive(PartialEq, Clone, Copy)] + enum ParseState { + Init, + Key, + Value, + Quotes, + BackSlash, + WaitSpace, + Invalid, + } + + let mut state = ParseState::Init; + let mut state_before_back_slash = ParseState::Value; + let mut key = String::new(); + let mut value = String::new(); + let mut res: HashMap = HashMap::new(); + for c in s.chars() { + match state { + ParseState::Init => { + if !key.is_empty() && !value.is_empty() { + res.insert(key, value); + } + key = String::new(); + value = String::new(); + if c.is_ascii_alphanumeric() || c == '_' { + key += &c.to_string(); + state = ParseState::Key; + } else if c != ' ' { + state = ParseState::Invalid; + break; + } + } + ParseState::Key => { + if c.is_ascii_alphanumeric() || c == '_' { + key += &c.to_string(); + } else if c == '=' { + state = ParseState::Value; + } else { + /* F-O=foo */ + state = ParseState::Invalid; + break; + } + } + ParseState::Value => { + /* FOO="foo bar" */ + if c == '\"' { + state = ParseState::Quotes; + continue; + } + /* FOO==\"foo */ + if c == '\\' { + state = ParseState::BackSlash; + state_before_back_slash = ParseState::Value; + continue; + } + if c != ' ' { + value += &c.to_string(); + continue; + } + state = ParseState::Init; + } + ParseState::BackSlash => { + /* FOO=\"foo or FOO="\"foo bar" */ + value += &c.to_string(); + state = state_before_back_slash; + } + ParseState::Quotes => { + /* We have got the right ", there must a space after. */ + if c == '\"' { + state = ParseState::WaitSpace; + continue; + } + if c == '\\' { + state = ParseState::BackSlash; + state_before_back_slash = ParseState::Quotes; + continue; + } + value += &c.to_string(); + } + ParseState::WaitSpace => { + if c != ' ' { + /* FOO="foo bar"x */ + state = ParseState::Invalid; + break; + } else { + state = ParseState::Init; + } + } + ParseState::Invalid => { + break; + } + } + } + if state == ParseState::Invalid { + log::warn!("Found invalid Environment, breaking"); + return Ok(res); + } + if !key.is_empty() + && !value.is_empty() + && [ParseState::Init, ParseState::WaitSpace, ParseState::Value].contains(&state) + { + res.insert(key, value); + } + Ok(res) +} + impl ExecContext { /// create a new instance of exec context pub fn new() -> ExecContext { @@ -360,7 +592,7 @@ impl ExecContext { /// pub fn set_group(&self, group_str: &str) -> Result<()> { - /* add_user should be called before add_group */ + /* set_user should be called before add_group */ assert!(self.user().is_some()); /* group is not configured, use the primary group of user */ @@ -688,4 +920,31 @@ mod tests { let rlimit = Rlimit::from_str(source5); assert!(rlimit.is_err()); } + + use crate::exec::base::parse_working_directory; + use std::path::PathBuf; + #[test] + fn test_parse_working_directory() { + assert_eq!( + parse_working_directory("/root").unwrap().directory(), + Some(PathBuf::from("/root")) + ); + assert_eq!( + parse_working_directory("-/root/foooooooobarrrrrr") + .unwrap() + .directory(), + Some(PathBuf::from("/root/foooooooobarrrrrr")) + ); + assert_eq!( + parse_working_directory("--------------/usr/lib") + .unwrap() + .directory(), + Some(PathBuf::from("/usr/lib")) + ); + assert_eq!( + parse_working_directory("~").unwrap().directory(), + Some(PathBuf::from(std::env::var("HOME").unwrap())) + ); + assert_eq!(parse_working_directory("").unwrap().directory(), None); + } } diff --git a/core/libcore/src/exec/cmd.rs b/core/libcore/src/exec/cmd.rs index c3887035e6e3508d73f58d8de92acc696feca41e..af1fb7d947131d1a524343caecd026609d050865 100644 --- a/core/libcore/src/exec/cmd.rs +++ b/core/libcore/src/exec/cmd.rs @@ -14,6 +14,7 @@ use crate::serialize::DeserializeWith; use crate::specifier::{ unit_string_specifier_escape, unit_strings_specifier_escape, UnitSpecifierData, }; +use basic::fs_util::{path_is_abosolute, path_simplify}; use basic::{fs_util::parse_absolute_path, Error, Result}; use bitflags::bitflags; use serde::{ @@ -62,7 +63,7 @@ impl ExecCommand { pub fn empty() -> ExecCommand { ExecCommand { path: String::new(), - argv: vec![String::new()], + argv: Vec::new(), flags: ExecFlag::EXEC_COMMAND_EMPTY, } } @@ -71,10 +72,35 @@ impl ExecCommand { self.flags |= flag; } /// + pub fn append_argv(&mut self, argv: &str) { + self.argv.push(String::from(argv)); + } + /// + pub fn append_many_argv(&mut self, argvs: Vec<&str>) { + self.argv.extend(argvs.iter().map(|x| x.to_string())) + } + /// pub fn get_exec_flag(&self) -> ExecFlag { self.flags } - + /// + pub fn set_path(&mut self, path: &str) -> Result<()> { + if !path_is_abosolute(path) { + return Err(Error::Invalid { + what: "ExecCmd path should be absolute".to_string(), + }); + } + let v = match path_simplify(path) { + None => { + return Err(Error::Invalid { + what: "Invalid ExecCmd path".to_string(), + }) + } + Some(v) => v, + }; + self.path = v; + Ok(()) + } /// return the path of the command pub fn path(&self) -> &String { &self.path @@ -102,7 +128,7 @@ impl ExecCommand { } /// -pub fn deserialize_exec_command(s: &str) -> Result> { +pub fn parse_exec_command(s: &str) -> Result> { match parse_exec(s) { Ok(v) => Ok(v), Err(e) => { diff --git a/core/libcore/src/exec/mod.rs b/core/libcore/src/exec/mod.rs index ad8463e46bf505618a46df079114355a181e473c..ba009f619297dfbe0350391eca8079cfb58600d8 100644 --- a/core/libcore/src/exec/mod.rs +++ b/core/libcore/src/exec/mod.rs @@ -14,9 +14,12 @@ mod base; mod cmd; pub use base::{ - ExecContext, ExecDirectoryType, ExecFlags, ExecParameters, Rlimit, RuntimeDirectory, - StateDirectory, WorkingDirectory, + parse_environment, parse_runtime_directory, parse_state_directory, parse_working_directory, }; -pub use cmd::deserialize_exec_command; +pub use base::{ + ExecContext, ExecDirectoryType, ExecFlags, ExecParameters, PreserveMode, Rlimit, + RuntimeDirectory, StateDirectory, WorkingDirectory, +}; +pub use cmd::parse_exec_command; pub use cmd::ExecCommand; pub use cmd::ExecFlag; diff --git a/core/libcore/src/unit/base.rs b/core/libcore/src/unit/base.rs index dfe073ab8a34bf48734474d2bc760e0b5b83c041..3f0df840d9e29ea9d912ac0ef05d3341507cc306 100644 --- a/core/libcore/src/unit/base.rs +++ b/core/libcore/src/unit/base.rs @@ -131,12 +131,6 @@ pub trait SubUnit: ReStation + UnitMngUtil { Vec::new() } - /// - fn set_socket_fd(&self, _fd: i32) {} - - /// - fn release_socket_fd(&self, _fd: i32) {} - /// fn trigger(&self, _other: &str) {} @@ -166,6 +160,24 @@ pub trait SubUnit: ReStation + UnitMngUtil { fn get_perpetual(&self) -> bool { false } + + // ================ ONLY VALID FOR SERVICE ================ + /// + fn set_socket_fd(&self, _fd: i32) {} + + /// + fn release_socket_fd(&self, _fd: i32) {} + + // ================ ONLY VALID FOR MOUNT ================ + /// + fn setup_existing_mount(&self, _what: &str, _mount_where: &str, _options: &str, _fstype: &str) { + } + + /// + fn setup_new_mount(&self, _what: &str, _mount_where: &str, _options: &str, _fstype: &str) {} + + /// + fn update_mount_state(&self, _state: &str) {} } /// the macro for create a sub unit instance with dyn ref of UmIf, @@ -219,16 +231,13 @@ pub fn unit_name_is_valid(name: &str, flag: UnitNameFlags) -> bool { None => return false, Some(v) => (v.0, v.1), }; - let unit_type = match unit_type_from_string(last_name) { Err(_) => return false, Ok(v) => v, }; - if unit_type == UnitType::UnitTypeInvalid { return false; } - match first_name.split_once('@') { None => flag.contains(UnitNameFlags::PLAIN), Some(v) => { diff --git a/core/libcore/src/unit/umif.rs b/core/libcore/src/unit/umif.rs index 89b41dac81cb4eb455c7875c1cbfb02dc36b6712..62df8215ddbfba1c02c1ca3af373df87d24e1693 100644 --- a/core/libcore/src/unit/umif.rs +++ b/core/libcore/src/unit/umif.rs @@ -205,6 +205,32 @@ pub trait UmIf { /// release the service's socket fd fn service_release_socket_fd(&self, _service_name: &str, _fd: i32) {} + + /* ========== ONLY VALID IN MOUNT ========== */ + /// setup existing mount + fn setup_existing_mount( + &self, + _unit_name: &str, + _what: &str, + _mount_where: &str, + _options: &str, + _fstype: &str, + ) { + } + + /// setup new mount + fn setup_new_mount( + &self, + _unit_name: &str, + _what: &str, + _mount_where: &str, + _options: &str, + _fstype: &str, + ) { + } + + /// update mount state + fn update_mount_state(&self, _unit_name: &str, _state: &str) {} } /// the trait used for attach UnitManager to sub unit diff --git a/core/sysmaster/src/unit/entry/uentry.rs b/core/sysmaster/src/unit/entry/uentry.rs index 2b1115d3438385da1f289186cd28a19796b52aac..25d2adf2473c4bf70132d3838e2ba46df9191e5d 100644 --- a/core/sysmaster/src/unit/entry/uentry.rs +++ b/core/sysmaster/src/unit/entry/uentry.rs @@ -603,12 +603,6 @@ impl Unit { pub(super) fn load_unit(&self) -> Result<()> { self.set_in_load_queue(false); - // Mount unit doesn't have config file, set its loadstate to - // UnitLoaded directly. - if self.unit_type() == UnitType::UnitMount { - self.load.set_load_state(UnitLoadState::Loaded); - return Ok(()); - } match self.load.load_unit_confs() { Ok(_) => { let paths = self.load.get_unit_id_fragment_pathbuf(); @@ -793,6 +787,31 @@ impl Unit { self.sub.release_socket_fd(fd) } + pub(crate) fn setup_existing_mount( + &self, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) { + self.sub + .setup_existing_mount(what, mount_where, options, fstype); + } + + pub(crate) fn setup_new_mount( + &self, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) { + self.sub.setup_new_mount(what, mount_where, options, fstype); + } + + pub(crate) fn update_mount_state(&self, state: &str) { + self.sub.update_mount_state(state); + } + pub(crate) fn notify_message( &self, ucred: &UnixCredentials, diff --git a/core/sysmaster/src/unit/execute/spawn.rs b/core/sysmaster/src/unit/execute/spawn.rs index 60a87070ef09efb035f566cff078a04e820df132..01651289dd5764d72c588e7c8f3bf02cebe8d73d 100644 --- a/core/sysmaster/src/unit/execute/spawn.rs +++ b/core/sysmaster/src/unit/execute/spawn.rs @@ -65,7 +65,7 @@ impl ExecSpawn { fn apply_user_and_group(exec_ctx: Rc, params: &ExecParameters) -> Result<()> { let user = match exec_ctx.user() { - // ExecParameters.add_user() has already assigned valid user if the configuration is correct + // ExecParameters.set_user() has already assigned valid user if the configuration is correct None => { return Err(Error::InvalidData); } diff --git a/core/sysmaster/src/unit/manager.rs b/core/sysmaster/src/unit/manager.rs index 3f2da448ae085a273666276d892d37849b902879..18cfb231be9f1635ac8a857f0c04325640b35893 100644 --- a/core/sysmaster/src/unit/manager.rs +++ b/core/sysmaster/src/unit/manager.rs @@ -657,6 +657,60 @@ impl UmIf for UnitManager { }; service.release_socket_fd(fd); } + + /// setup existing mount + fn setup_existing_mount( + &self, + unit_name: &str, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) { + let mount = match self.units_get(unit_name) { + None => { + log::error!("Failed to get unit: {}", unit_name); + return; + } + Some(v) => v, + }; + mount.setup_existing_mount(what, mount_where, options, fstype); + } + + /// setup new mount + fn setup_new_mount( + &self, + unit_name: &str, + what: &str, + mount_where: &str, + options: &str, + fstype: &str, + ) { + let mount = match self.load_unitx(unit_name) { + None => { + log::error!("Failed to load a new mount {}", unit_name); + return; + } + Some(v) => { + log::info!("Created a new mount {}", unit_name); + v + } + }; + mount.setup_new_mount(what, mount_where, options, fstype); + mount.set_load_state(UnitLoadState::Loaded); + } + + /// update mount state + fn update_mount_state(&self, unit_name: &str, state: &str) { + let mount = match self.units_get(unit_name) { + None => { + log::error!("Failed to update the mount state of {}", unit_name); + return; + } + Some(v) => v, + }; + mount.update_mount_state(state); + } } /// the declaration "pub(self)" is for identification only. diff --git a/libs/basic/src/condition.rs b/libs/basic/src/condition.rs index ab4b8842a93dc3afda9ac529e7da4683ccdba022..39d09e5f9b862246c2543116390d099b2e5f6122 100644 --- a/libs/basic/src/condition.rs +++ b/libs/basic/src/condition.rs @@ -21,7 +21,10 @@ use nix::{ use libc::{glob, glob_t, GLOB_NOSORT}; -use crate::{cmdline, fd_util, mount_util::is_mount_point, security, sysfs::SysFs, unistd}; +use crate::{ + cmdline, fd_util, fs_util::directory_is_not_empty, mount_util::is_mount_point, security, + sysfs::SysFs, unistd, +}; #[cfg(target_env = "musl")] use crate::mount_util::MountInfoParser; @@ -192,17 +195,7 @@ impl Condition { } 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 + directory_is_not_empty(Path::new(&self.params)) as i8 } fn test_file_is_executable(&self) -> i8 { diff --git a/libs/basic/src/fs_util.rs b/libs/basic/src/fs_util.rs index 4a47eb1d91f51aa1cf3fc7e59b8e64c84d381710..dcc110ae1df55281f6a2b7fbff96d6e074f97de1 100644 --- a/libs/basic/src/fs_util.rs +++ b/libs/basic/src/fs_util.rs @@ -14,6 +14,7 @@ use crate::Error; use crate::{error::*, format_proc_fd_path}; use libc::{fchownat, mode_t, timespec, AT_EMPTY_PATH, S_IFLNK, S_IFMT}; +use nix::unistd::mkdir; use nix::{ fcntl::{open, readlink, renameat, OFlag}, sys::stat::{fstat, Mode}, @@ -310,6 +311,76 @@ pub fn futimens_opath(fd: RawFd, ts: Option<[timespec; 2]>) -> Result<()> { Ok(()) } +/// mkdir -p with the given directory mode +pub fn mkdir_p_label(path: &Path, mode: u32) -> Result<()> { + let path_str = path.to_string_lossy(); + if path.exists() && path.is_dir() { + if let Err(e) = chmod(&path_str, mode) { + log::error!("Failed to chmod of {}: {}", path_str, e); + return Err(e); + } + } + + let simplified_path = match path_simplify(&path_str) { + None => { + return Err(Error::Invalid { + what: format!("Invalid path: {}", path_str), + }); + } + Some(v) => v, + }; + + if !path_is_abosolute(&simplified_path) { + return Err(Error::Invalid { + what: format!( + "Invalid Path: {}, only abosolute path is allowed.", + path_str + ), + }); + } + + let mode = Mode::from_bits_truncate(mode); + let mut cur_path = PathBuf::from("/"); + // mkdir -p up to down + for e in simplified_path.split('/') { + cur_path = cur_path.join(e); + if cur_path.exists() { + continue; + } + if let Err(e) = mkdir(&cur_path, mode) { + return Err(Error::Nix { source: e }); + } + } + Ok(()) +} + +/// mkdir parents with the given label +pub fn mkdir_parents_label() {} + +/// check if the given directory is not empty +pub fn directory_is_not_empty(path: &Path) -> bool { + if path.is_file() { + return false; + } + let mut iter = match path.read_dir() { + Err(_) => return false, + Ok(v) => v, + }; + iter.next().is_some() +} + +/// check if the given directory is empty +pub fn directory_is_empty(path: &Path) -> bool { + if path.is_file() { + return false; + } + let mut iter = match path.read_dir() { + Err(_) => return false, + Ok(v) => v, + }; + iter.next().is_none() +} + /// recursively remove parent directories until specific directory pub fn remove_dir_until(path: &str, stop: &str) -> Result<()> { let path = Path::new(path); @@ -554,6 +625,11 @@ pub fn path_simplify(p: &str) -> Option { Some(res) } +/// check if the given string is a path +pub fn is_path(s: &str) -> bool { + s.contains('/') +} + /// check if the given path is abololute path pub fn path_is_abosolute(s: &str) -> bool { s.starts_with('/') @@ -591,6 +667,14 @@ pub fn parse_absolute_path(s: &str) -> Result { Ok(path) } +/// +pub fn parse_pathbuf(s: &str) -> Result { + let path = parse_absolute_path(s).map_err(|_| Error::Invalid { + what: "Invalid PathBuf".to_string(), + })?; + Ok(PathBuf::from(path)) +} + /// unit lookup path in /etc pub const ETC_SYSTEM_PATH: &str = "/etc/sysmaster/system"; /// unit lookup path in /run diff --git a/libs/basic/src/lib.rs b/libs/basic/src/lib.rs index 3aabd0213469c4cfb4c008929d47ef87eacc36b4..5007047c0754c1ae3a9a3fcde08f1b52f42fca05 100644 --- a/libs/basic/src/lib.rs +++ b/libs/basic/src/lib.rs @@ -124,6 +124,9 @@ pub const EXEC_RUNTIME_PREFIX: &str = "/run"; /// the default mount command path pub const MOUNT_BIN: &str = "/usr/bin/mount"; +/// the default umount command path +pub const UMOUNT_BIN: &str = "/usr/bin/umount"; + /// the default swapon path pub const SWAP_BIN: &str = "/usr/sbin/swapon"; diff --git a/libs/basic/src/mount_util.rs b/libs/basic/src/mount_util.rs index b3de62a7bbed6e6dfb34c7ae5e5862bf69c73326..004386d54f579109d8c671799d683eedf524163d 100644 --- a/libs/basic/src/mount_util.rs +++ b/libs/basic/src/mount_util.rs @@ -306,6 +306,30 @@ impl Iterator for MountInfoParser { } } +/// return the unit name of a mount point +pub fn mount_point_to_unit_name(mount_point: &str) -> String { + let mut res = String::from(mount_point).replace('/', "-") + ".mount"; + if res != "-.mount" { + res = String::from(&res[1..]) + } + res +} + +/// filter options we don't need, and return the rest +pub fn filter_options(options: &str, filter_names: Vec<&str>) -> String { + let mut res = String::new(); + for option in options.split(',') { + if filter_names.contains(&option.trim()) { + continue; + } + if !res.is_empty() { + res += "," + } + res += option + } + res +} + ///Read the file of filename into the BufReader for later processing pub fn read_lines

(filename: P) -> io::Result>> where