diff --git a/Cargo.lock b/Cargo.lock index fe5b58c6a2805f236f1ce1b70df87dd9f7608fd6..47074ee44dfc88c900ff0816c2b1819986e4cd70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1338,6 +1338,23 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path" +version = "0.5.1" +dependencies = [ + "basic", + "constants", + "core", + "event", + "libc", + "log 0.5.1", + "macros", + "nix 0.24.3", + "once_cell", + "serde", + "unit_parser", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -1878,6 +1895,7 @@ dependencies = [ "mount", "nix 0.24.3", "once_cell", + "path", "regex", "serde", "service", diff --git a/core/coms/path/Cargo.toml b/core/coms/path/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9a2573456876a7a5a96270cab4bce2ae55c9e034 --- /dev/null +++ b/core/coms/path/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "path" +version = "0.5.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["dylib", "lib"] +name = "path" + +[dependencies] +basic = { path = "../../../libs/basic", default-features = false, features = [ + "mount", + "glob", +] } +core = { path = "../../libcore", default-features = false } +event = { path = "../../../libs/event" } +log = { path = "../../../libs/log" } +macros = { path = "../../../libs/macros" } +constants = { path = "../../../libs/constants" } +unit_parser = { path = "../../../libs/unit_parser" } +libc = { version = "0.2.*", default-features = false } +nix = { version = "0.24", default-features = false, features = [ + "fs", + "resource", + "poll", +] } +once_cell = { version = "=1.8.0", default-features = false } +serde = { version = "1.0.130", default-features = false } + +[features] +default = ["noplugin"] +noplugin = [] +linux = [] +plugin = [] diff --git a/core/coms/path/src/base.rs b/core/coms/path/src/base.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d569468ffad7c8a8deaa5d86f055e84e5e6aa1c --- /dev/null +++ b/core/coms/path/src/base.rs @@ -0,0 +1,14 @@ +// 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. + +#[cfg(feature = "plugin")] +pub(super) const PLUGIN_NAME: &str = "PathUnit"; diff --git a/core/coms/path/src/comm.rs b/core/coms/path/src/comm.rs new file mode 100644 index 0000000000000000000000000000000000000000..ffec1054d249559e842ea9815e1faa3b3b6e1f97 --- /dev/null +++ b/core/coms/path/src/comm.rs @@ -0,0 +1,196 @@ +// 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 comm module provides management of common objects, mainly including weak references to UnitManager and Unit objects. +//! The method provided by the public object needs to be called. +//! +use super::rentry::{PathRe, PathResult, PathState, SectionPath}; +use core::rel::Reliability; +use core::unit::{PathType, UmIf, UnitBase}; +use once_cell::sync::Lazy; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; +use std::sync::{Arc, RwLock}; + +pub(super) struct PathUnitComm { + owner: RefCell>>, + umcomm: Arc, +} + +impl PathUnitComm { + pub(super) fn new() -> Self { + PathUnitComm { + owner: RefCell::new(None), + umcomm: PathUmComm::get_instance(), + } + } + + pub(super) fn attach_unit(&self, unit: Rc) { + self.owner.replace(Some(Rc::downgrade(&unit))); + } + + pub(super) fn attach_um(&self, um: Rc) { + self.umcomm.attach_um(um) + } + + pub(super) fn attach_reli(&self, reli: Rc) { + self.umcomm.attach_reli(reli); + } + + pub(super) fn owner(&self) -> Option> { + if let Some(ref unit) = *self.owner.borrow() { + unit.upgrade() + } else { + None + } + } + + pub(super) fn get_owner_id(&self) -> String { + self.owner().map_or_else(|| "None".to_string(), |u| u.id()) + } + pub(super) fn um(&self) -> Rc { + self.umcomm.um() + } + + pub(super) fn rentry_conf_insert(&self, path: &SectionPath) { + if let Some(u) = self.owner() { + self.rentry().conf_insert(&u.id(), path) + } + } + + pub(super) fn rentry_conf_get(&self) -> Option { + self.owner().map(|u| self.rentry().conf_get(&u.id()))? + } + + pub(super) fn rentry_mng_insert( + &self, + state: PathState, + result: PathResult, + path_spec: Vec<(PathType, bool, String)>, + ) { + if let Some(u) = self.owner() { + self.rentry().mng_insert(&u.id(), state, result, path_spec) + } + } + + pub(super) fn rentry_mng_get(&self) -> Option<(PathState, PathResult)> { + self.owner().map(|u| self.rentry().mng_get(&u.id()))? + } + + pub(super) fn _reli(&self) -> Rc { + self.umcomm._reli() + } + + fn rentry(&self) -> Rc { + self.umcomm.rentry() + } +} + +static PATH_UM_COMM: Lazy> = Lazy::new(|| { + let comm = PathUmComm::new(); + Arc::new(comm) +}); + +pub(super) struct PathUmComm { + data: RwLock, +} + +unsafe impl Send for PathUmComm {} + +unsafe impl Sync for PathUmComm {} + +impl PathUmComm { + pub(super) fn new() -> Self { + PathUmComm { + data: RwLock::new(PathUmCommData::new()), + } + } + + pub(super) fn attach_um(&self, um: Rc) { + let mut wdata = self.data.write().unwrap(); + wdata.attach_um(um); + } + + pub(super) fn attach_reli(&self, reli: Rc) { + let mut wdata = self.data.write().unwrap(); + wdata.attach_reli(reli); + } + + pub(super) fn get_instance() -> Arc { + PATH_UM_COMM.clone() + } + + pub(super) fn _reli(&self) -> Rc { + let rdata = self.data.read().unwrap(); + rdata._reli() + } + + pub(super) fn um(&self) -> Rc { + let rdata = self.data.read().unwrap(); + rdata.um().unwrap() + } + + pub(super) fn rentry(&self) -> Rc { + let rdata = self.data.read().unwrap(); + rdata.rentry() + } +} + +struct PathUmCommData { + // associated objects + um: Option>, + reli: Weak, + rentry: Option>, +} + +// the declaration "pub(self)" is for identification only. +impl PathUmCommData { + pub(self) fn new() -> PathUmCommData { + PathUmCommData { + um: None, + reli: Weak::new(), + rentry: None, + } + } + + pub(self) fn attach_um(&mut self, um: Rc) { + if self.um.is_none() { + log::debug!("PathUmCommData attach_um action."); + self.um = Some(um); + } + } + + pub(self) fn attach_reli(&mut self, reli: Rc) { + let old = self.reli.clone().upgrade(); + if old.is_none() { + log::debug!("PathUmCommData attach_reli action."); + self.reli = Rc::downgrade(&reli); + self.rentry.replace(Rc::new(PathRe::new(&reli))); + } + } + + pub(self) fn um(&self) -> Option> { + if let Some(ref um) = self.um { + Some(Rc::clone(um)) + } else { + None + } + } + + pub(self) fn _reli(&self) -> Rc { + self.reli.clone().upgrade().unwrap() + } + + pub(self) fn rentry(&self) -> Rc { + self.rentry.as_ref().cloned().unwrap() + } +} diff --git a/core/coms/path/src/config.rs b/core/coms/path/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..70f6eb856429c583a2d9d8e8bb446a1316ea977f --- /dev/null +++ b/core/coms/path/src/config.rs @@ -0,0 +1,97 @@ +// 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. +// + +//! path_config mod load the conf file list and convert it to structure which is defined in this mod. +//! +#![allow(non_snake_case)] +use crate::{comm::PathUnitComm, rentry::SectionPath}; +use core::error::*; +use core::rel::ReStation; +use std::{cell::RefCell, path::PathBuf, rc::Rc}; +use unit_parser::prelude::UnitConfig; + +#[derive(UnitConfig, Default)] +#[allow(non_snake_case)] +pub(super) struct PathConfigData { + pub Path: SectionPath, +} + +impl PathConfigData { + pub(self) fn new(Path: SectionPath) -> PathConfigData { + PathConfigData { Path } + } +} + +pub(super) struct PathConfig { + // associated objects + comm: Rc, + + // owned objects + data: Rc>, +} + +impl ReStation for PathConfig { + // no input, no compensate + + // data + fn db_map(&self, reload: bool) { + if reload { + return; + } + + if let Some(data) = self.comm.rentry_conf_get() { + // PathConfigData + self.data.replace(PathConfigData::new(data)); + } + } + + fn db_insert(&self) { + self.comm.rentry_conf_insert(&self.data.borrow().Path); + } + + // reload: no external connections, no entry +} + +impl PathConfig { + pub(super) fn new(commr: &Rc) -> Self { + PathConfig { + comm: Rc::clone(commr), + data: Rc::new(RefCell::new(PathConfigData::default())), + } + } + + pub(super) fn load(&self, paths: Vec, name: &str, update: bool) -> Result<()> { + let data = match PathConfigData::load_config(paths, name) { + Ok(v) => v, + Err(e) => { + log::error!("Invalid Configuration: {}", e); + return Err(Error::ConfigureError { + msg: format!("Invalid Configuration: {}", e), + }); + } + }; + + // record original configuration + *self.data.borrow_mut() = data; + + if update { + self.db_update(); + } + + Ok(()) + } + + pub(super) fn config_data(&self) -> Rc> { + self.data.clone() + } +} diff --git a/core/coms/path/src/lib.rs b/core/coms/path/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e6f1f2a1992eb04cea9077603ebd4900a83f07a --- /dev/null +++ b/core/coms/path/src/lib.rs @@ -0,0 +1,51 @@ +// 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. + +//! Path is one of the unit types supported in sysmaster. Path units use the inotify(7) API to monitor file systems, and pull up the corresponding service when the conditions are met +//! The Path configuration file contains three sections: Unit,Path,and Install. +//! +//! # Example: +//! ``` toml +//! [Unit] +//! Description=test path +//! +//! [Path] +//! PathExists=/tmp/PathExists +//! PathExistsGlob=/tmp/PathExistsGlo* +//! PathChanged=/tmp/PathChanged +//! PathModified=/tmp/PathModified +//! DirectoryNotEmpty=/tmp/DirectoryNotEmpty +//! Unit=test.service +//! MakeDirectory=yes +//! DirectoryMode=0644 +//! +//! [Install] +//! WantedBy="paths.target" +//! ``` +//! `[Path]` section related configuration +//! + +#[cfg(all(feature = "plugin", feature = "noplugin"))] +compile_error!("feature plugin and noplugin cannot be enabled at the same time"); + +pub use {manager::__um_obj_create, unit::__subunit_create_with_params}; + +// dependency: +// base -> rentry -> {comm | config} +// mng -> unit -> manager +mod base; +mod comm; +mod config; +mod manager; +mod mng; +mod rentry; +mod unit; diff --git a/core/coms/path/src/manager.rs b/core/coms/path/src/manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..92bdeefa0c724193dfba3df2625d6b70328e1ff4 --- /dev/null +++ b/core/coms/path/src/manager.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. + +#[cfg(feature = "plugin")] +use crate::base::PLUGIN_NAME; +#[cfg(feature = "plugin")] +use constants::LOG_FILE_PATH; + +use super::comm::PathUmComm; +use core::rel::{ReStation, Reliability}; +use core::unit::{UmIf, UnitManagerObj, UnitMngUtil}; +use std::rc::Rc; +use std::sync::Arc; +struct PathManager { + comm: Arc, +} + +// the declaration "pub(self)" is for identification only. +impl PathManager { + pub(self) fn new() -> PathManager { + let _comm = PathUmComm::get_instance(); + PathManager { + comm: Arc::clone(&_comm), + } + } +} + +impl UnitManagerObj for PathManager { + // nothing to customize +} + +impl ReStation for PathManager { + // no input, no compensate + + // no data + + // reload: no external connections, no entry +} + +impl UnitMngUtil for PathManager { + fn attach_um(&self, um: Rc) { + self.comm.attach_um(um) + } + + fn attach_reli(&self, reli: Rc) { + self.comm.attach_reli(reli); + } +} + +use core::declare_umobj_plugin; +declare_umobj_plugin!(PathManager, PathManager::new); diff --git a/core/coms/path/src/mng.rs b/core/coms/path/src/mng.rs new file mode 100644 index 0000000000000000000000000000000000000000..20695056072e451e049e12115f3e2e45d429f96a --- /dev/null +++ b/core/coms/path/src/mng.rs @@ -0,0 +1,493 @@ +// 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. + +//! path_mng is the core of the path unit,implement the state transition, event management and sub child management. +//! + +use super::comm::PathUnitComm; +use super::config::PathConfig; +use super::rentry::{PathResult, PathState}; +use basic::fd::close; +use basic::fs::{directory_is_not_empty, mkdir_p_label}; +use basic::glob::glob_first; +use basic::IN_SET; +use constants::INVALID_FD; +use core::error::*; +use core::rel::ReStation; +use core::unit::{PathSpec, PathType}; +use core::unit::{UnitActiveState, UnitNotifyFlags}; +use event::EventState; +use event::{EventType, Events, Source}; +use nix::unistd::{access, AccessFlags}; +use std::cell::RefCell; +use std::fmt; +use std::os::unix::prelude::RawFd; +use std::path::Path; +use std::rc::{Rc, Weak}; + +impl PathState { + fn to_unit_active_state(self) -> UnitActiveState { + match self { + PathState::Dead => UnitActiveState::InActive, + PathState::Waiting => UnitActiveState::Active, + PathState::Running => UnitActiveState::Active, + PathState::Failed => UnitActiveState::Failed, + } + } +} + +pub(crate) struct PathMng { + // associated objects + comm: Rc, + config: Rc, + + // owned objects + all_inotify: RefCell>>, + state: RefCell, + result: RefCell, +} + +impl ReStation for PathMng { + // input: do nothing + + // compensate: do nothing + + // data + fn db_map(&self, _reload: bool) { + if let Some((state, result)) = self.comm.rentry_mng_get() { + *self.state.borrow_mut() = state; + *self.result.borrow_mut() = result; + } + } + + fn db_insert(&self) { + self.comm.rentry_mng_insert( + self.state(), + self.result(), + self.specs() + .iter() + .map(|p| { + ( + p.path_type(), + p.previous_exists(), + p.path().to_str().unwrap().to_string(), + ) + }) + .collect::<_>(), + ); + } + + // reload: entry-only + fn entry_coldplug(&self) { + if self.state() == PathState::Dead { + return; + } + + if IN_SET!(self.state(), PathState::Waiting, PathState::Running) { + self.enter_waiting(true, false) + } + } + + fn entry_clear(&self) { + self.unwatch(); + } +} + +// the declaration "pub(self)" is for identification only. +impl PathMng { + pub(crate) fn new(commr: &Rc, configr: &Rc) -> PathMng { + PathMng { + comm: Rc::clone(commr), + config: Rc::clone(configr), + all_inotify: RefCell::new(Vec::new()), + state: RefCell::new(PathState::Dead), + result: RefCell::new(PathResult::Success), + } + } + + pub(crate) fn start_action(&self) { + if !IN_SET!(self.state(), PathState::Dead, PathState::Failed) { + return; + } + + let u = match self.comm.owner() { + None => return, + Some(u) => u, + }; + if !self.comm.um().test_trigger_loaded(&u.id()) { + return; + } + + // TODO: unit_acquire_invocation_id; + + self.path_mkdir(); + + *self.result.borrow_mut() = PathResult::Success; + + self.enter_waiting(true, false); + + self.db_update(); + } + + pub(crate) fn stop_action(&self) { + if !IN_SET!(self.state(), PathState::Waiting, PathState::Running) { + return; + } + + self.enter_dead(PathResult::Success); + self.db_update(); + } + + pub(crate) fn push_inotify(&self, inotify: Rc) { + self.all_inotify.borrow_mut().push(inotify); + self.db_update(); + } + + pub fn all_inotify(&self) -> Vec> { + self.all_inotify.borrow().iter().cloned().collect::<_>() + } + + fn specs(&self) -> Vec> { + self.all_inotify + .borrow() + .iter() + .map(|p| Rc::clone(&p.spec())) + .collect::<_>() + } + + pub(crate) fn reset_failed(&self) { + if self.state() == PathState::Failed { + self.set_state(PathState::Dead); + } + self.set_result(PathResult::Success); + } + + pub(crate) fn state(&self) -> PathState { + *self.state.borrow() + } + + pub(crate) fn get_state(&self) -> String { + let state = *self.state.borrow(); + state.to_string() + } + + fn set_state(&self, state: PathState) { + let old_state = self.state(); + self.state.replace(state); + + if !IN_SET!(state, PathState::Waiting, PathState::Running) { + self.unwatch(); + } + + if state != old_state { + log::debug!("Changed {} -> {}", old_state.to_string(), state.to_string()); + } + + if let Some(u) = self.comm.owner() { + u.notify( + old_state.to_unit_active_state(), + state.to_unit_active_state(), + UnitNotifyFlags::EMPTY, + ) + } + } + + fn result(&self) -> PathResult { + *self.result.borrow() + } + + fn set_result(&self, res: PathResult) { + *self.result.borrow_mut() = res; + } + + fn db_update(&self) { + self.db_insert(); + } + + pub(super) fn current_active_state(&self) -> UnitActiveState { + self.state().to_unit_active_state() + } + + fn watch(&self) { + let events = self.comm.um().events(); + for inotify in self.all_inotify() { + if inotify.watch().is_err() { + self.unwatch(); + return; + } + if events.add_source(inotify.clone()).is_err() { + self.unwatch(); + return; + } + if events.set_enabled(inotify.clone(), EventState::On).is_err() { + self.unwatch(); + return; + } + } + } + + fn unwatch(&self) { + let events = self.comm.um().events(); + for inotify in self.all_inotify().iter() { + let source = Rc::clone(inotify); + events.set_enabled(source, EventState::Off).unwrap(); + + close(inotify.fd()); + inotify.spec().set_inotify_fd(INVALID_FD); + } + } + + fn path_mkdir(&self) { + if !self.config.config_data().borrow().Path.MakeDirectory { + return; + } + + for inotify in self.all_inotify() { + if IN_SET!( + inotify.spec().path_type(), + PathType::Exists, + PathType::ExistsGlob + ) { + continue; + } + + if let Err(e) = mkdir_p_label( + Path::new(&inotify.spec().path()), + self.config.config_data().borrow().Path.DirectoryMode, + ) { + log::error!("mkdir({:?}) failed: {}", inotify.spec().path(), e); + } + } + } + + pub fn enter_waiting(&self, initial: bool, from_trigger_notify: bool) { + let u = match self.comm.owner() { + None => return, + Some(u) => u, + }; + let um = self.comm.um(); + let trigger = um.unit_get_trigger(&u.id()); + + /* If the triggered unit is already running, so are we */ + if !trigger.is_empty() && !um.current_active_state(&trigger).is_inactive_or_failed() { + self.set_state(PathState::Running); + self.unwatch(); + return; + } + + if let Some(trigger_path) = self.check_good(initial, from_trigger_notify) { + log::debug!("{} Got triggered.", self.comm.get_owner_id()); + self.enter_running(&trigger_path); + return; + } + + self.watch(); + + /* The file might have appeared/been removed during the preparation for watch, so we must recheck. */ + if let Some(trigger_path) = self.check_good(false, from_trigger_notify) { + log::debug!("{} Got triggered.", self.comm.get_owner_id()); + self.enter_running(&trigger_path); + return; + } + + self.set_state(PathState::Waiting); + } + + fn enter_running(&self, _trigger_path: &str) { + let u = match self.comm.owner() { + None => return, + Some(u) => u, + }; + + let um = self.comm.um(); + if um.has_stop_job(&self.comm.owner().unwrap().id()) { + return; + } + + let trigger = um.unit_get_trigger(&u.id()); + if trigger.is_empty() { + log::error!("{} Unit to trigger vanished.", u.id()); + self.enter_dead(PathResult::FailureResources); + return; + } + + if let Err(err) = um.unit_start_by_job(&trigger) { + log::error!("Failed to queue unit startup job: {:?}", err); + self.enter_dead(PathResult::FailureResources); + } + + self.set_state(PathState::Running); + self.unwatch(); + } + + fn enter_dead(&self, result: PathResult) { + if self.result() == PathResult::Success { + self.set_result(result); + } + + if self.result() != PathResult::Success { + self.set_state(PathState::Failed) + } else { + self.set_state(PathState::Dead) + } + } + + fn check_good(&self, initial: bool, from_trigger_notify: bool) -> Option { + for inotify in self.all_inotify() { + if let Some(trigger_path) = spec_check_good(&inotify.spec, initial, from_trigger_notify) + { + return Some(trigger_path); + } + } + + None + } +} + +fn spec_check_good( + spec: &Rc, + initial: bool, + from_trigger_notify: bool, +) -> Option { + let mut trigger = String::new(); + let mut good = false; + + match spec.path_type() { + PathType::Exists => { + if let Ok(()) = access(spec.path().as_path(), AccessFlags::F_OK) { + good = true; + } + } + + PathType::ExistsGlob => { + if let Some(s) = spec.path().as_path().to_str() { + match glob_first(s) { + Ok(first) => { + good = true; + trigger = first; + } + Err(_) => good = false, + } + } + } + + PathType::DirectoryNotEmpty => { + good = match directory_is_not_empty(Path::new(spec.path().as_path())) { + Ok(flag) => flag, + Err(err) => !IN_SET!(err.get_errno(), libc::ENOENT, libc::ENOTDIR), + }; + } + + PathType::Changed | PathType::Modified => { + let b = match access(spec.path().as_path(), AccessFlags::F_OK) { + Ok(()) => true, + Err(_) => false, + }; + + good = !initial && !from_trigger_notify && b != spec.previous_exists(); + spec.set_previous_exists(b); + } + _ => {} + } + + if good { + if trigger.is_empty() { + match spec.path().to_str() { + Some(path) => return Some(path.to_string()), + None => return None, + } + } + return Some(trigger); + } + + None +} + +pub struct PathInotify { + // associated objects + mng: Weak, + + // owned objects + spec: Rc, +} + +impl PathInotify { + pub(crate) fn new(mng: &Rc, spec: Rc) -> PathInotify { + PathInotify { + mng: Rc::downgrade(mng), + spec, + } + } + + pub(crate) fn spec(&self) -> Rc { + self.spec.clone() + } + + pub(crate) fn watch(&self) -> Result<()> { + self.spec.watch() + } + + fn mng(&self) -> Rc { + self.mng.clone().upgrade().unwrap() + } +} + +impl fmt::Display for PathInotify { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.spec) + } +} + +impl Source for PathInotify { + fn fd(&self) -> RawFd { + self.spec.inotify_fd() + } + + fn event_type(&self) -> EventType { + EventType::Io + } + + fn epoll_event(&self) -> u32 { + (libc::EPOLLIN) as u32 + } + + fn priority(&self) -> i8 { + 0i8 + } + + fn dispatch(&self, _event: &Events) -> i32 { + if !IN_SET!(self.mng().state(), PathState::Waiting, PathState::Running) { + return 0; + } + + match self.spec.read_fd_event() { + Ok(changed) => { + if changed { + self.mng().enter_running(self.spec.path().to_str().unwrap()) + } else { + self.mng().enter_waiting(false, false) + } + } + Err(_) => { + self.mng().enter_dead(PathResult::FailureResources); + return 0; + } + } + + 0 + } + + fn token(&self) -> u64 { + let data: u64 = unsafe { std::mem::transmute(self) }; + data + } +} diff --git a/core/coms/path/src/rentry.rs b/core/coms/path/src/rentry.rs new file mode 100644 index 0000000000000000000000000000000000000000..04d8b4d1cc03269f1ef364b03845830a5ffb276c --- /dev/null +++ b/core/coms/path/src/rentry.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. +// +#![allow(non_snake_case)] +use basic::time::USEC_INFINITY; +use core::exec::parse_mode; +use core::rel::{ReDb, ReDbRwTxn, ReDbTable, ReliSwitch, Reliability}; +use core::unit::PathType; +use macros::{EnumDisplay, UnitSection}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::rc::Rc; + +const RELI_DB_HPATH_CONF: &str = "pathconf"; +const RELI_DB_HPATH_MNG: &str = "pathmng"; + +#[derive(UnitSection, Serialize, Deserialize, Debug, Default, Clone)] +pub struct SectionPath { + #[entry(append)] + pub PathExists: Vec, + #[entry(append)] + pub PathExistsGlob: Vec, + #[entry(append)] + pub PathChanged: Vec, + #[entry(append)] + pub PathModified: Vec, + #[entry(append)] + pub DirectoryNotEmpty: Vec, + #[entry(default = String::new())] + pub Unit: String, + #[entry(default = false)] + pub MakeDirectory: bool, + #[entry(default = 0o755, parser = parse_mode)] + pub DirectoryMode: u32, + + /// TODO: TriggerLimitIntervalSec= + #[entry(default = USEC_INFINITY)] + pub TriggerLimitIntervalSec: u64, + + /// TODO: TriggerLimitBurst= + #[entry(default = u32::MAX)] + pub TriggerLimitBurst: u32, +} + +#[derive(PartialEq, Eq, Debug, Copy, Clone, Serialize, Deserialize, EnumDisplay)] +pub(crate) enum PathState { + Dead, + Waiting, + Running, + Failed, +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] +pub(super) enum PathResult { + Success, + FailureResources, + FailureStartLimitHit, + FailureUnitStartLimitHit, + FailureTriggerLimitHit, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct PathReConf { + path: SectionPath, +} + +impl PathReConf { + fn new(pathr: &SectionPath) -> PathReConf { + PathReConf { + path: pathr.clone(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct PathReMng { + state: PathState, + result: PathResult, + path_spec: Vec<(PathType, bool, String)>, +} + +impl PathReMng { + fn new( + state: PathState, + result: PathResult, + path_spec: Vec<(PathType, bool, String)>, + ) -> PathReMng { + PathReMng { + state, + result, + path_spec, + } + } +} + +struct PathReDb(ReDb); + +pub(super) struct PathRe { + // database: multi-instance(N) + conf: Rc>, // RELI_DB_HSOCKET_CONF; key: unit_id, data: config; + mng: Rc>, // RELI_DB_HSOCKET_MNG; key: unit_id, data: state+result+pathspec; +} + +impl PathRe { + pub(super) fn new(relir: &Rc) -> PathRe { + let conf = Rc::new(PathReDb(ReDb::new(relir, RELI_DB_HPATH_CONF))); + let mng = Rc::new(PathReDb(ReDb::new(relir, RELI_DB_HPATH_MNG))); + let rentry = PathRe { conf, mng }; + rentry.register(relir); + rentry + } + + pub(super) fn conf_insert(&self, unit_id: &str, path: &SectionPath) { + let conf = PathReConf::new(path); + self.conf.0.insert(unit_id.to_string(), conf); + } + + pub(super) fn _conf_remove(&self, unit_id: &str) { + self.conf.0.remove(&unit_id.to_string()); + } + + pub(super) fn conf_get(&self, unit_id: &str) -> Option { + let conf = self.conf.0.get(&unit_id.to_string()); + conf.map(|c| (c.path)) + } + + pub(super) fn mng_insert( + &self, + unit_id: &str, + state: PathState, + result: PathResult, + path_spec: Vec<(PathType, bool, String)>, + ) { + let mng = PathReMng::new(state, result, path_spec); + self.mng.0.insert(unit_id.to_string(), mng); + } + + pub(super) fn _mng_remove(&self, unit_id: &str) { + self.mng.0.remove(&unit_id.to_string()); + } + + pub(super) fn mng_get(&self, unit_id: &str) -> Option<(PathState, PathResult)> { + let mng = self.mng.0.get(&unit_id.to_string()); + mng.map(|m| (m.state, m.result)) + } + + fn register(&self, relir: &Reliability) { + // rel-db: RELI_DB_HPATH_CONF + let db = Rc::clone(&self.conf); + relir.history_db_register(RELI_DB_HPATH_CONF, db); + + // rel-db: RELI_DB_HPATH_MNG + let db = Rc::clone(&self.mng); + relir.history_db_register(RELI_DB_HPATH_MNG, db); + } +} + +impl ReDbTable for PathReDb { + fn clear(&self, wtxn: &mut ReDbRwTxn) { + self.0.do_clear(wtxn); + } + + fn export(&self, db_wtxn: &mut ReDbRwTxn) { + self.0.cache_2_db(db_wtxn); + } + + fn flush(&self, db_wtxn: &mut ReDbRwTxn, switch: ReliSwitch) { + self.0.data_2_db(db_wtxn, switch); + } + + fn import<'a>(&self) { + self.0.db_2_cache(); + } + + fn switch_set(&self, switch: ReliSwitch) { + self.0.switch_buffer(switch); + } +} + +impl ReDbTable for PathReDb { + fn clear(&self, wtxn: &mut ReDbRwTxn) { + self.0.do_clear(wtxn); + } + + fn export(&self, db_wtxn: &mut ReDbRwTxn) { + self.0.cache_2_db(db_wtxn); + } + + fn flush(&self, db_wtxn: &mut ReDbRwTxn, switch: ReliSwitch) { + self.0.data_2_db(db_wtxn, switch); + } + + fn import<'a>(&self) { + self.0.db_2_cache(); + } + + fn switch_set(&self, switch: ReliSwitch) { + self.0.switch_buffer(switch); + } +} diff --git a/core/coms/path/src/unit.rs b/core/coms/path/src/unit.rs new file mode 100644 index 0000000000000000000000000000000000000000..c466e34507beba721d469800086356580bedd9c9 --- /dev/null +++ b/core/coms/path/src/unit.rs @@ -0,0 +1,374 @@ +// 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. + +//! mount unit is entry of mount type of unit,need impl +//! UnitObj,UnitMngUtil, UnitSubClass trait + +use super::comm::PathUnitComm; +use super::mng::PathMng; +use crate::config::PathConfig; +use crate::mng::PathInotify; +use crate::rentry::PathState; +use basic::fs::{path_length_is_valid, path_name_is_safe, path_simplify}; +use basic::{IN_SET, PATHS_TARGET, SHUTDOWN_TARGET, SYSINIT_TARGET}; +use core::error::*; +use core::rel::{ReStation, Reliability}; +use core::unit::unit_name_to_type; +use core::unit::PathSpec; +use core::unit::{ + PathType, SubUnit, UmIf, UnitActiveState, UnitBase, UnitDependencyMask, UnitMngUtil, + UnitRelations, UnitType, +}; +use nix::sys::wait::WaitStatus; +use nix::NixPath; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; +struct PathUnit { + comm: Rc, + mng: Rc, + config: Rc, +} + +impl ReStation for PathUnit { + // no input, no compensate + + // data + fn db_map(&self, reload: bool) { + self.config.db_map(reload); + if !reload { + self.build_mspecs().unwrap(); + } + self.mng.db_map(reload); + } + + fn db_insert(&self) { + self.config.db_insert(); + self.mng.db_insert(); + } + + // reload: no external connections, entry-only + fn entry_coldplug(&self) { + // rebuild external connections, like: timer, ... + self.mng.entry_coldplug(); + } + + fn entry_clear(&self) { + // release external connection, like: timer, ... + self.mng.entry_clear(); + } +} + +impl PathUnit { + fn new(_um: Rc) -> PathUnit { + let _comm = Rc::new(PathUnitComm::new()); + let _config = Rc::new(PathConfig::new(&_comm)); + PathUnit { + comm: Rc::clone(&_comm), + mng: Rc::new(PathMng::new(&_comm, &_config)), + config: Rc::clone(&_config), + } + } + + fn build_mspecs(&self) -> Result<()> { + self.parse_specs( + &self.config.config_data().borrow().Path.PathExists, + PathType::Exists, + )?; + + self.parse_specs( + &self.config.config_data().borrow().Path.PathExistsGlob, + PathType::ExistsGlob, + )?; + + self.parse_specs( + &self.config.config_data().borrow().Path.DirectoryNotEmpty, + PathType::DirectoryNotEmpty, + )?; + + self.parse_specs( + &self.config.config_data().borrow().Path.PathChanged, + PathType::Changed, + )?; + + self.parse_specs( + &self.config.config_data().borrow().Path.PathModified, + PathType::Modified, + ) + } + + fn parse_specs(&self, paths: &[PathBuf], path_type: PathType) -> Result<()> { + for path in paths { + if path.is_empty() { + continue; + } + + let s = path.to_str().unwrap(); + + if !path_name_is_safe(s) { + log::error!("{:?} contains invalid character: {}", path_type, s); + return Err(Error::Nix { + source: nix::Error::EINVAL, + }); + } + + if !path_length_is_valid(s) { + log::error!("{:?} is too long: {}", path_type, s); + return Err(Error::Nix { + source: nix::Error::EINVAL, + }); + } + + let s = match path_simplify(s) { + None => { + log::error!("{:?} is not valid: {}", path_type, s); + return Err(Error::Nix { + source: nix::Error::EINVAL, + }); + } + Some(v) => v, + }; + + if !path.is_absolute() { + log::error!("{:?} path is not absolute, ignoring: {}", path_type, s); + return Err(Error::Nix { + source: nix::Error::EINVAL, + }); + } + + let spec = Rc::new(PathSpec::new(path.to_path_buf(), path_type)); + let inotify = Rc::new(PathInotify::new(&self.mng, spec)); + self.mng.push_inotify(inotify); + } + + Ok(()) + } + + fn verify(&self) -> Result<()> { + if self.mng.all_inotify().is_empty() { + log::error!("Path unit lacks path setting. Refusing."); + return Err(Error::Nix { + source: nix::Error::ENOEXEC, + }); + } + + Ok(()) + } + + fn add_extras(&self) -> Result<()> { + let um = self.comm.um(); + let u = match self.comm.owner() { + None => { + return Ok(()); + } + Some(v) => v, + }; + + self.add_trigger_dependencies(&um, &u)?; + + // TODO: add_mount_dependencies() + + self.add_default_dependencies(&um, &u) + } + + fn add_trigger_dependencies(&self, um: &Rc, u: &Rc) -> Result<()> { + if !um.unit_get_trigger(&u.id()).is_empty() { + return Ok(()); + } + + let name = self.load_related_unit(UnitType::UnitService, u)?; + um.unit_add_two_dependency( + &u.id(), + UnitRelations::UnitBefore, + UnitRelations::UnitTriggers, + &name, + true, + UnitDependencyMask::Implicit, + ) + } + + fn load_related_unit(&self, related_type: UnitType, u: &Rc) -> Result { + let path_unit = self.config.config_data().borrow().Path.Unit.clone(); + if path_unit.is_empty() { + let unit_name = u.id(); + let suffix = String::from(related_type); + if suffix.is_empty() { + return Err(format!("failed to load related unit {}", suffix).into()); + } + if unit_name.is_empty() { + return Err( + format!("failed to load related unit {} unit name is empty", suffix).into(), + ); + } + let u_name = unit_name; + let stem_name = Path::new(&u_name).file_stem().unwrap().to_str().unwrap(); + let relate_name = format!("{}.{}", stem_name, suffix); + return Ok(relate_name); + } + + let path_unit_type = unit_name_to_type(&path_unit); + if path_unit_type == UnitType::UnitTypeInvalid { + return Err(format!("Unit {} type not valid, ignoring", path_unit).into()); + } + if path_unit == u.id() { + return Err(format!("Units {} cannot trigger themselves, ignoring", path_unit).into()); + } + + Ok(path_unit) + } + + fn add_default_dependencies(&self, um: &Rc, u: &Rc) -> Result<()> { + if !u.default_dependencies() { + return Ok(()); + } + + um.unit_add_dependency( + &u.id(), + UnitRelations::UnitBefore, + PATHS_TARGET, + true, + UnitDependencyMask::Default, + )?; + + um.unit_add_two_dependency( + &u.id(), + UnitRelations::UnitAfter, + UnitRelations::UnitRequires, + SYSINIT_TARGET, + true, + UnitDependencyMask::Default, + )?; + + um.unit_add_two_dependency( + &u.id(), + UnitRelations::UnitBefore, + UnitRelations::UnitConflicts, + SHUTDOWN_TARGET, + true, + UnitDependencyMask::Default, + ) + } +} + +impl SubUnit for PathUnit { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn load(&self, paths: Vec) -> Result<()> { + let unit_name = self.comm.get_owner_id(); + self.config.load(paths, &unit_name, true)?; + + self.add_extras()?; + + self.build_mspecs()?; + + self.verify() + } + + fn current_active_state(&self) -> UnitActiveState { + self.mng.current_active_state() + } + + fn get_subunit_state(&self) -> String { + self.mng.get_state() + } + + fn attach_unit(&self, unit: Rc) { + self.comm.attach_unit(unit); + self.db_insert(); + } + + fn init(&self) {} + + fn done(&self) {} + + fn dump(&self) {} + + fn start(&self) -> Result<()> { + log::info!("Path start {:?}", self.comm.get_owner_id()); + + self.mng.start_action(); + Ok(()) + } + + fn stop(&self, _force: bool) -> Result<()> { + self.mng.stop_action(); + Ok(()) + } + + fn trigger(&self, other: &str) { + /* Invoked whenever the unit we trigger changes state or gains or loses a job */ + + /* Don't propagate state changes from the triggered unit if we are already down */ + if !IN_SET!(self.mng.state(), PathState::Waiting, PathState::Running) { + return; + } + + /* TODO: Propagate start limit hit state */ + + /* Don't propagate anything if there's still a job queued */ + let um = self.comm.um(); + if um.has_job(other) { + return; + } + + if self.mng.state() == PathState::Running + && um.current_active_state(other).is_inactive_or_failed() + { + log::debug!( + "{}: Got notified about unit deactivation.", + self.comm.get_owner_id() + ); + self.mng.enter_waiting(false, true); + } else if self.mng.state() == PathState::Waiting + && um.current_active_state(other).is_inactive_or_failed() + { + log::debug!( + "{}: Got notified about unit activation.", + self.comm.get_owner_id() + ); + self.mng.enter_waiting(false, true); + } + } + + fn kill(&self) {} + + fn release_resources(&self) {} + + fn sigchld_events(&self, _wait_status: WaitStatus) {} + + fn reset_failed(&self) { + self.mng.reset_failed() + } +} + +impl UnitMngUtil for PathUnit { + fn attach_um(&self, um: Rc) { + self.comm.attach_um(um); + } + + fn attach_reli(&self, reli: Rc) { + self.comm.attach_reli(reli); + } +} + +/* +impl Default for PathUnit { + fn default() -> Self { + PathUnit::new() + } +} +*/ + +use core::declare_unitobj_plugin_with_param; +declare_unitobj_plugin_with_param!(PathUnit, PathUnit::new); diff --git a/core/coms/service/src/mng.rs b/core/coms/service/src/mng.rs index 710507aeba30ee15ee38a01ca6844d6aece385e9..4dcdc0d876a9ceef0d5eb6a1e42140795a6206f3 100644 --- a/core/coms/service/src/mng.rs +++ b/core/coms/service/src/mng.rs @@ -10,8 +10,6 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use crate::monitor::ServiceMonitor; - use super::comm::ServiceUnitComm; use super::config::ServiceConfig; use super::pid::ServicePid; @@ -19,18 +17,18 @@ use super::rentry::{ NotifyState, ServiceCommand, ServiceRestart, ServiceResult, ServiceState, ServiceType, }; use super::spawn::ServiceSpawn; +use crate::monitor::ServiceMonitor; use crate::rentry::{ExitStatus, NotifyAccess}; -use basic::{do_entry_log, fd, IN_SET}; +use basic::{do_entry_log, IN_SET}; use basic::{fs, process}; use core::error::*; use core::exec::{ExecCommand, ExecContext, ExecFlag, ExecFlags, PreserveMode}; use core::rel::ReStation; use core::unit::{KillOperation, UnitActiveState, UnitNotifyFlags}; +use core::unit::{PathSpec, PathType}; use event::{EventState, EventType, Events, Source}; use log::Level; -use nix::errno::Errno; use nix::libc; -use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor}; use nix::sys::signal::Signal; use nix::sys::socket::UnixCredentials; use nix::sys::wait::WaitStatus; @@ -38,13 +36,9 @@ use nix::unistd::Pid; use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; use std::fmt; -use std::os::unix::prelude::AsRawFd; -use std::rc::Rc; -use std::{ - os::unix::prelude::{FromRawFd, RawFd}, - path::PathBuf, - rc::Weak, -}; +use std::os::unix::prelude::RawFd; +use std::path::PathBuf; +use std::rc::{Rc, Weak}; pub(super) struct ServiceMng { // associated objects @@ -1118,7 +1112,8 @@ impl ServiceMng { } fn demand_pid_file(&self) -> Result<()> { - let pid_file_inotify = PathInotify::new(self.config.pid_file().unwrap()); + let pid_file_inotify = + PathInotify::new(self.config.pid_file().unwrap(), PathType::Modified); self.rd.attach_inotify(Rc::new(pid_file_inotify)); @@ -1128,7 +1123,7 @@ impl ServiceMng { fn watch_pid_file(&self) -> Result<()> { let pid_file_inotify = self.rd.path_inotify(); log::debug!("watch pid file: {}", pid_file_inotify); - match pid_file_inotify.add_watch_path() { + match pid_file_inotify.watch() { Ok(_) => { let events = self.comm.um().events(); let source = Rc::clone(&pid_file_inotify); @@ -1144,8 +1139,8 @@ impl ServiceMng { Err(e) => { log::debug!( - "failed to add watch for pid file {:?}, err: {}", - pid_file_inotify.path, + "failed to add watch for pid file {}, err: {}", + pid_file_inotify, e ); self.unwatch_pid_file(); @@ -2202,39 +2197,21 @@ impl Rtdata { } } -#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] -enum PathType { - Changed, - Modified, -} - struct PathInotify { - path: PathBuf, - p_type: PathType, - inotify: RefCell, - wd: RefCell>, + spec: PathSpec, mng: RefCell>, } impl fmt::Display for PathInotify { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "path: {:?}, path type: {:?}, inotify fd: {}", - self.path, - self.p_type, - *self.inotify.borrow() - ) + write!(f, "{}", self.spec) } } impl PathInotify { - fn new(path: PathBuf) -> Self { + fn new(path: PathBuf, p_type: PathType) -> Self { PathInotify { - path, - p_type: PathType::Modified, - inotify: RefCell::new(-1), - wd: RefCell::new(None), + spec: PathSpec::new(path, p_type), mng: RefCell::new(Weak::new()), } } @@ -2244,102 +2221,12 @@ impl PathInotify { *self.mng.borrow_mut() = mng; } - fn add_watch_path(&self) -> Result { - self.unwatch(); - - let inotify = Inotify::init(InitFlags::all()).map_err(|_e| Error::Other { - msg: "create initofy fd err".to_string(), - })?; - *self.inotify.borrow_mut() = inotify.as_raw_fd(); - - let ansters = self.path.as_path().ancestors(); - let mut primary: bool = true; - let mut flags: AddWatchFlags; - - let mut exist = false; - for anster in ansters { - flags = if primary { - AddWatchFlags::IN_DELETE_SELF - | AddWatchFlags::IN_MOVE_SELF - | AddWatchFlags::IN_ATTRIB - | AddWatchFlags::IN_CLOSE_WRITE - | AddWatchFlags::IN_CREATE - | AddWatchFlags::IN_DELETE - | AddWatchFlags::IN_MOVED_FROM - | AddWatchFlags::IN_MOVED_TO - | AddWatchFlags::IN_MODIFY - } else { - AddWatchFlags::IN_DELETE_SELF - | AddWatchFlags::IN_MOVE_SELF - | AddWatchFlags::IN_ATTRIB - | AddWatchFlags::IN_CREATE - | AddWatchFlags::IN_MOVED_TO - }; - - log::debug!( - "inotify fd is: {}, flags is: {:?}, path: {:?}", - *self.inotify.borrow(), - flags, - anster - ); - - match inotify.add_watch(anster, flags) { - Ok(wd) => { - if primary { - *self.wd.borrow_mut() = Some(wd); - } - - exist = true; - break; - } - Err(err) => { - log::error!("watch on path {:?} error: {:?}", anster, err); - } - } - - primary = false; - } - - if !exist { - return Err(Error::Other { - msg: "watch on any of the ancestor failed".to_string(), - }); - } - - Ok(true) + fn watch(&self) -> Result<()> { + self.spec.watch() } fn unwatch(&self) { - fd::close(*self.inotify.borrow()); - *self.inotify.borrow_mut() = -1; - } - - fn read_fd_event(&self) -> Result { - let inotify = unsafe { Inotify::from_raw_fd(*self.inotify.borrow_mut()) }; - let events = match inotify.read_events() { - Ok(events) => events, - Err(e) => { - if e == Errno::EAGAIN || e == Errno::EINTR { - return Ok(false); - } - - return Err(Error::Other { - msg: "read evnets from inotify error".to_string(), - }); - } - }; - - if IN_SET!(self.p_type, PathType::Changed, PathType::Modified) { - for event in events { - if let Some(ref wd) = *self.wd.borrow() { - if event.wd == *wd { - return Ok(true); - } - } - } - } - - Ok(false) + self.spec.unwatch() } pub(self) fn mng(&self) -> Rc { @@ -2347,8 +2234,8 @@ impl PathInotify { } fn do_dispatch(&self) -> i32 { - log::debug!("dispatch inotify pid file: {:?}", self.path); - match self.read_fd_event() { + log::debug!("dispatch inotify pid file: {:?}", self.spec.path()); + match self.spec.read_fd_event() { Ok(_) => { if let Ok(_v) = self.mng().retry_pid_file() { return 0; @@ -2373,7 +2260,7 @@ impl PathInotify { impl Source for PathInotify { fn fd(&self) -> RawFd { - *self.inotify.borrow() + self.spec.inotify_fd() } fn event_type(&self) -> EventType { diff --git a/core/coms/socket/src/rentry.rs b/core/coms/socket/src/rentry.rs index 1de3231e9e369a1302969ee21313e05270bbae4a..98575aed59094f8b17b30fbc75234521e26d506f 100644 --- a/core/coms/socket/src/rentry.rs +++ b/core/coms/socket/src/rentry.rs @@ -41,12 +41,6 @@ fn parse_pathbuf_vec(s: &str) -> Result, core::error::Error> { Ok(res) } -fn deserialize_parse_mode(s: &str) -> Result { - u32::from_str_radix(s, 8).map_err(|_| core::error::Error::ConfigureError { - msg: format!("Invalid SocketMode: {}", s), - }) -} - fn deserialize_netlink_vec(s: &str) -> Result, core::error::Error> { Ok(vec![s.to_string()]) } @@ -97,7 +91,7 @@ pub(super) struct SectionSocket { #[entry(append, parser = parse_pathbuf_vec)] pub Symlinks: Vec, pub PassSecurity: Option, - #[entry(default = 0o666, parser = deserialize_parse_mode)] + #[entry(default = 0o666, parser = core::exec::parse_mode)] pub SocketMode: u32, #[entry(default = String::new())] pub SocketUser: String, @@ -415,20 +409,3 @@ impl ReDbTable for SocketReDb { self.0.switch_buffer(switch); } } - -#[cfg(test)] - -mod test { - use super::deserialize_parse_mode; - - #[test] - fn test_deserialize_parse_mode() { - assert_eq!(deserialize_parse_mode("777").unwrap(), 0o777); - assert_eq!(deserialize_parse_mode("644").unwrap(), 0o644); - assert!(deserialize_parse_mode("-777").is_err()); - assert!(deserialize_parse_mode("787").is_err()); - assert!(deserialize_parse_mode("777aa").is_err()); - assert!(deserialize_parse_mode("aaaaa").is_err()); - assert!(deserialize_parse_mode("777 aa").is_err()); - } -} diff --git a/core/coms/target/src/unit.rs b/core/coms/target/src/unit.rs index 4a9e8359001eb280aa9416d1c54c4d9eb3511529..7a70bd57e0409e30737805bdbd63fdd0c08d9fa4 100644 --- a/core/coms/target/src/unit.rs +++ b/core/coms/target/src/unit.rs @@ -99,7 +99,7 @@ impl Target { } /* Don't create loop, as we will add UnitAfter later. */ - if um.unit_has_dependecy(&u.id(), UnitRelationAtom::UnitAtomBefore, &other) { + if um.unit_has_dependency(&u.id(), UnitRelationAtom::UnitAtomBefore, &other) { continue; } diff --git a/core/libcore/Cargo.toml b/core/libcore/Cargo.toml index 3711b223800b25d4c8f7d53f0d4f1fc7b173c4bb..933142091465bc0ee10cea015d91ac9c073b6872 100644 --- a/core/libcore/Cargo.toml +++ b/core/libcore/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] # internal libraries basic = { path = "../../libs/basic", default-features = false, features = [ + "fd", "fs", "rlimit", "show_table", diff --git a/core/libcore/src/exec/base.rs b/core/libcore/src/exec/base.rs index 4f885663784b02cd729c56c8c60affeb18862fb6..dfda9c0136bac40e8285f380b64657c572ef254d 100644 --- a/core/libcore/src/exec/base.rs +++ b/core/libcore/src/exec/base.rs @@ -823,6 +823,13 @@ bitflags! { } } +/// parse file mode bits +pub fn parse_mode(s: &str) -> Result { + u32::from_str_radix(s, 8).map_err(|_| Error::ConfigureError { + msg: format!("Invalid SocketMode: {}", s), + }) +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -947,4 +954,16 @@ mod tests { ); assert_eq!(parse_working_directory("").unwrap().directory(), None); } + + use super::parse_mode; + #[test] + fn test_parse_mode() { + assert_eq!(parse_mode("777").unwrap(), 0o777); + assert_eq!(parse_mode("644").unwrap(), 0o644); + assert!(parse_mode("-777").is_err()); + assert!(parse_mode("787").is_err()); + assert!(parse_mode("777aa").is_err()); + assert!(parse_mode("aaaaa").is_err()); + assert!(parse_mode("777 aa").is_err()); + } } diff --git a/core/libcore/src/exec/mod.rs b/core/libcore/src/exec/mod.rs index ba009f619297dfbe0350391eca8079cfb58600d8..3cf10b6b77cece63abf3e03aebe309dd3a83a60a 100644 --- a/core/libcore/src/exec/mod.rs +++ b/core/libcore/src/exec/mod.rs @@ -14,7 +14,8 @@ mod base; mod cmd; pub use base::{ - parse_environment, parse_runtime_directory, parse_state_directory, parse_working_directory, + parse_environment, parse_mode, parse_runtime_directory, parse_state_directory, + parse_working_directory, }; pub use base::{ ExecContext, ExecDirectoryType, ExecFlags, ExecParameters, PreserveMode, Rlimit, diff --git a/core/libcore/src/unit/deps.rs b/core/libcore/src/unit/deps.rs index 4eeedc8ad5133c5725a3208c96a92808eca3f5c1..876dfef165cfbfb16eb88acc0f562b142f685834 100644 --- a/core/libcore/src/unit/deps.rs +++ b/core/libcore/src/unit/deps.rs @@ -115,6 +115,7 @@ pub enum UnitType { UnitSocket, UnitMount, UnitTimer, + UnitPath, UnitTypeMax, UnitTypeInvalid, UnitTypeErrnoMax, @@ -129,6 +130,7 @@ impl UnitType { UnitType::UnitSocket, UnitType::UnitMount, UnitType::UnitTimer, + UnitType::UnitPath, ] .iter() .copied() @@ -145,6 +147,7 @@ impl FromStr for UnitType { "socket" => UnitType::UnitSocket, "mount" => UnitType::UnitMount, "timer" => UnitType::UnitTimer, + "path" => UnitType::UnitPath, _ => UnitType::UnitTypeInvalid, }; Ok(ret) @@ -159,6 +162,7 @@ impl From for String { UnitType::UnitSocket => "socket".into(), UnitType::UnitMount => "mount".into(), UnitType::UnitTimer => "timer".into(), + UnitType::UnitPath => "path".into(), UnitType::UnitTypeMax => null_str!(""), UnitType::UnitTypeInvalid => null_str!(""), UnitType::UnitTypeErrnoMax => null_str!(""), @@ -175,7 +179,17 @@ impl TryFrom for UnitType { 2 => Ok(UnitType::UnitSocket), 3 => Ok(UnitType::UnitMount), 4 => Ok(UnitType::UnitTimer), + 5 => Ok(UnitType::UnitPath), v => Err(format!("input {} is invalid", v)), } } } + +/// parse UnitType by unit_name +pub fn unit_name_to_type(unit_name: &str) -> UnitType { + let words: Vec<&str> = unit_name.split('.').collect(); + if words.is_empty() { + return UnitType::UnitTypeInvalid; + } + UnitType::from_str(words[words.len() - 1]).unwrap_or(UnitType::UnitTypeInvalid) +} diff --git a/core/libcore/src/unit/mod.rs b/core/libcore/src/unit/mod.rs index 1eb565c4986aa07a2be356aa9590f12003c70eee..a043be4bb02fc515d9f9cd1286305c6140e372c3 100644 --- a/core/libcore/src/unit/mod.rs +++ b/core/libcore/src/unit/mod.rs @@ -12,13 +12,15 @@ //! pub use base::{unit_name_is_valid, SubUnit, UnitBase, UnitNameFlags}; -pub use deps::{UnitDependencyMask, UnitRelationAtom, UnitRelations, UnitType}; +pub use deps::{unit_name_to_type, UnitDependencyMask, UnitRelationAtom, UnitRelations, UnitType}; pub use kill::{KillContext, KillMode, KillOperation}; +pub use path_spec::{PathSpec, PathType}; pub use state::{UnitActiveState, UnitNotifyFlags, UnitStatus}; pub use umif::{UmIf, UnitManagerObj, UnitMngUtil}; mod base; mod deps; mod kill; +mod path_spec; mod state; mod umif; diff --git a/core/libcore/src/unit/path_spec.rs b/core/libcore/src/unit/path_spec.rs new file mode 100644 index 0000000000000000000000000000000000000000..bd7a6e51773a35f125b84c61500cbd6ac0475ac8 --- /dev/null +++ b/core/libcore/src/unit/path_spec.rs @@ -0,0 +1,277 @@ +// 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. + +//! implement the management of configured path monitor +//! + +use crate::error::*; +use basic::fd::close; +use basic::{errno_is_transient, IN_SET}; +use constants::INVALID_FD; +use libc::{ + IN_ATTRIB, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE, IN_DELETE_SELF, IN_MODIFY, IN_MOVED_FROM, + IN_MOVED_TO, IN_MOVE_SELF, +}; +use nix::errno::Errno; +use nix::sys::inotify::AddWatchFlags; +use nix::sys::inotify::WatchDescriptor; +use nix::sys::inotify::{InitFlags, Inotify}; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::fmt; +use std::os::unix::prelude::{AsRawFd, FromRawFd, RawFd}; +use std::path::PathBuf; + +/// Path type +#[allow(missing_docs)] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum PathType { + Exists, + ExistsGlob, + DirectoryNotEmpty, + Changed, + Modified, + TypeMax, +} + +static FLAGS_TABLE: [u32; PathType::TypeMax as usize] = [ + IN_DELETE_SELF | IN_MOVE_SELF | IN_ATTRIB, + IN_DELETE_SELF | IN_MOVE_SELF | IN_ATTRIB, + IN_DELETE_SELF | IN_MOVE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO, + IN_DELETE_SELF + | IN_MOVE_SELF + | IN_ATTRIB + | IN_CLOSE_WRITE + | IN_CREATE + | IN_DELETE + | IN_MOVED_FROM + | IN_MOVED_TO, + IN_DELETE_SELF + | IN_MOVE_SELF + | IN_ATTRIB + | IN_CLOSE_WRITE + | IN_CREATE + | IN_DELETE + | IN_MOVED_FROM + | IN_MOVED_TO + | IN_MODIFY, +]; + +/// Path property configuration +pub struct PathSpec { + path: PathBuf, + p_type: PathType, + inotify_fd: RefCell, + primary_wd: RefCell>, + previous_exists: RefCell, +} + +impl PathSpec { + /// Create PathSpec + pub fn new(path: PathBuf, p_type: PathType) -> Self { + PathSpec { + path, + p_type, + inotify_fd: RefCell::new(INVALID_FD), + primary_wd: RefCell::new(None), + previous_exists: RefCell::new(false), + } + } + + /// get file path + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + /// get PathType + pub fn path_type(&self) -> PathType { + self.p_type + } + + /// get inotify_fd + pub fn inotify_fd(&self) -> RawFd { + *self.inotify_fd.borrow() + } + + /// set inotify_fd + pub fn set_inotify_fd(&self, inotify_fd: RawFd) { + *self.inotify_fd.borrow_mut() = inotify_fd; + } + + /// get primary_wd + pub fn primary_wd(&self) -> Option { + *self.primary_wd.borrow() + } + + /// set primary_wd + pub fn set_primary_wd(&self, primary_wd: Option) { + *self.primary_wd.borrow_mut() = primary_wd; + } + + /// get previous_exists + pub fn previous_exists(&self) -> bool { + *self.previous_exists.borrow() + } + + /// set previous_exists + pub fn set_previous_exists(&self, previous_exists: bool) { + *self.previous_exists.borrow_mut() = previous_exists; + } + + /// start file watch + pub fn watch(&self) -> Result<()> { + self.unwatch(); + + let inotify = + Inotify::init(InitFlags::IN_NONBLOCK | InitFlags::IN_CLOEXEC).map_err(|_e| { + Error::Other { + msg: "create initofy fd err".to_string(), + } + })?; + self.set_inotify_fd(inotify.as_raw_fd()); + + let mut ansters = self.path.as_path().ancestors().collect::>(); + ansters.reverse(); + + let mut flags: AddWatchFlags; + let mut wd: Option = None; + + let mut exists = false; + for anster in ansters { + let mut incomplete = false; + + flags = if anster != self.path.as_path() { + AddWatchFlags::IN_DELETE_SELF + | AddWatchFlags::IN_MOVE_SELF + | AddWatchFlags::IN_ATTRIB + | AddWatchFlags::IN_CREATE + | AddWatchFlags::IN_MOVED_TO + } else { + AddWatchFlags::from_bits_truncate(FLAGS_TABLE[self.p_type as usize]) + }; + + /* If this is a symlink watch both the symlink inode and where it points to. If the inode is + * not a symlink both calls will install the same watch, which is redundant and doesn't + * hurt. */ + for follow_symlink in 0..2 { + let mut f = flags; + if 0 == follow_symlink { + f |= AddWatchFlags::IN_DONT_FOLLOW; + } else { + f &= !AddWatchFlags::IN_DONT_FOLLOW; + } + + match inotify.add_watch(anster, flags) { + Ok(w) => wd = Some(w), + Err(err) => { + if IN_SET!(err, Errno::EACCES, Errno::ENOENT) { + /* This is an expected error, let's accept this + * quietly: we have an incomplete watch for now. */ + incomplete = true; + break; + } + + /* This second call to add_watch() should fail like the previous one + * and is done for logging the error in a comprehensive way. */ + match inotify.add_watch(anster, flags) { + Ok(w) => wd = Some(w), + Err(err) => { + self.unwatch(); + return Err(Error::Nix { source: err }); + } + } + + /* Succeeded in adding the watch this time. let's continue. */ + } + } + } + + if incomplete { + break; + } + + exists = true; + + /* Path exists, we don't need to watch parent too closely. */ + if anster.parent().is_some() { + let _ = inotify.add_watch(anster.parent().unwrap(), AddWatchFlags::IN_MOVE_SELF); + /* Error is ignored, the worst can happen is we get spurious events. */ + } + + if anster == self.path.as_path() { + *self.primary_wd.borrow_mut() = wd; + } + } + + if !exists { + log::error!( + "Failed to add watch on any of the components of: {:?}", + self.path + ); + self.unwatch(); + return Err(Error::Nix { + source: Errno::EACCES, + }); + } + + Ok(()) + } + + /// file unwatch + pub fn unwatch(&self) { + if INVALID_FD != self.inotify_fd() { + close(self.inotify_fd()); + self.set_inotify_fd(INVALID_FD) + } + } + + /// read file event + pub fn read_fd_event(&self) -> Result { + let inotify = unsafe { Inotify::from_raw_fd(self.inotify_fd()) }; + let events = match inotify.read_events() { + Ok(events) => events, + Err(e) => { + if errno_is_transient(e) { + return Ok(false); + } + + return Err(Error::Other { + msg: "read evnets from inotify error".to_string(), + }); + } + }; + + if IN_SET!(self.p_type, PathType::Changed, PathType::Modified) { + for event in events { + if let Some(ref wd) = self.primary_wd() { + if event.wd == *wd { + return Ok(true); + } + } + } + } + + Ok(false) + } +} + +impl fmt::Display for PathSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "path:{:?} type:{:?} inotify_fd:{:?}", + self.path(), + self.path_type(), + self.inotify_fd(), + ) + } +} diff --git a/core/libcore/src/unit/umif.rs b/core/libcore/src/unit/umif.rs index 2e74caf3c39defa4f0483bb0e15d25c5ec8967e5..1468bd9f6467cf1730af90106974db0999c845b7 100644 --- a/core/libcore/src/unit/umif.rs +++ b/core/libcore/src/unit/umif.rs @@ -44,7 +44,7 @@ pub trait UmIf { false } /// check the unit s_u_name and t_u_name have atom relation - fn unit_has_dependecy( + fn unit_has_dependency( &self, _s_u_name: &str, _atom: UnitRelationAtom, @@ -155,6 +155,16 @@ pub trait UmIf { /// call the exec spawn to start the child service fn trigger_unit(&self, _lunit: &str) {} + /// get trigger by id + fn unit_get_trigger(&self, _id: &str) -> String { + String::new() + } + + /// Tests whether the unit to trigger is loaded + fn test_trigger_loaded(&self, _id: &str) -> bool { + false + } + /// call the exec spawn to start the child service fn exec_spawn( &self, diff --git a/core/sysmaster/Cargo.toml b/core/sysmaster/Cargo.toml index d11d236d59c0bc6439b0f4d4a85485251ea368c9..e61de60936fa80618388447842e1bfb70d338f2b 100644 --- a/core/sysmaster/Cargo.toml +++ b/core/sysmaster/Cargo.toml @@ -141,8 +141,13 @@ path = "../coms/timer" optional = true default-features = false +[dependencies.path] +path = "../coms/path" +optional = true +default-features = false + [features] -default = ["linux", "noplugin", "mount", "socket", "service", "target", "timer"] +default = ["linux", "noplugin", "mount", "socket", "service", "target", "timer", "path"] hongmeng = [] linux = [] noplugin = [] diff --git a/core/sysmaster/src/manager/rentry.rs b/core/sysmaster/src/manager/rentry.rs index 610cecfb9b2b00da1e2af52881b63d49fb226c8a..3eb094372e76615571000b271e3b1f328e78cfa1 100644 --- a/core/sysmaster/src/manager/rentry.rs +++ b/core/sysmaster/src/manager/rentry.rs @@ -86,7 +86,11 @@ const RELI_DB_HTARGET_MNG: &str = "tarmng"; const RELI_DB_HTIMER_CONF: &str = "timerconf"; const RELI_DB_HTIMER_MNG: &str = "timermng"; -pub const RELI_HISTORY_MAX_DBS: u32 = 20; +/* path */ +const RELI_DB_HPATH_CONF: &str = "pathconf"; +const RELI_DB_HPATH_MNG: &str = "pathmng"; + +pub const RELI_HISTORY_MAX_DBS: u32 = 22; #[allow(dead_code)] static RELI_HISTORY_DB_NAME: [&str; RELI_HISTORY_MAX_DBS as usize] = [ RELI_DB_HJOB_TRIGGER, @@ -109,4 +113,6 @@ static RELI_HISTORY_DB_NAME: [&str; RELI_HISTORY_MAX_DBS as usize] = [ RELI_DB_HTARGET_MNG, RELI_DB_HTIMER_CONF, RELI_DB_HTIMER_MNG, + RELI_DB_HPATH_CONF, + RELI_DB_HPATH_MNG, ]; diff --git a/core/sysmaster/src/unit/manager.rs b/core/sysmaster/src/unit/manager.rs index 245b929b0f1beb8a07a2410cdca8076165b5ea23..6b356f1d32730a5adde66bfc20d3e5781335b18f 100644 --- a/core/sysmaster/src/unit/manager.rs +++ b/core/sysmaster/src/unit/manager.rs @@ -314,14 +314,18 @@ pub struct UnitManager { } impl UmIf for UnitManager { - /// check the unit s_u_name and t_u_name have atom relation - fn unit_has_dependecy(&self, s_u_name: &str, atom: UnitRelationAtom, t_u_name: &str) -> bool { + /// check the unit s_u_name and t_u_name have atom relation. If 't_u_name' is empty checks if the unit has any dependency of that atom. + fn unit_has_dependency(&self, s_u_name: &str, atom: UnitRelationAtom, t_u_name: &str) -> bool { let s_unit = if let Some(s_unit) = self.db.units_get(s_u_name) { s_unit } else { return false; }; + if t_u_name.is_empty() { + return true; + } + let t_unit = if let Some(unit) = self.db.units_get(t_u_name) { unit } else { @@ -477,6 +481,41 @@ impl UmIf for UnitManager { self.jm.trigger_unit(lunit) } + /// get trigger by id + fn unit_get_trigger(&self, id: &str) -> String { + let deps = self.get_dependency_list(id, UnitRelationAtom::UnitAtomTriggers); + + if !deps.is_empty() { + return deps[0].clone(); + } + + String::new() + } + + /// Tests whether the unit to trigger is loaded + fn test_trigger_loaded(&self, id: &str) -> bool { + let trigger = self.unit_get_trigger(id); + if trigger.is_empty() { + log::error!("{} Refusing to start, no unit to trigger.", id); + return false; + } + + match self.rentry.load_get(&trigger) { + Some(state) => { + if state == UnitLoadState::Loaded { + return true; + } + log::error!( + "{} Refusing to start, unit {} to trigger not loaded.", + id, + trigger, + ); + false + } + None => true, + } + } + /// call the exec spawn to start the child service fn exec_spawn( &self, diff --git a/core/sysmaster/src/unit/util/unit_om.rs b/core/sysmaster/src/unit/util/unit_om.rs index 1373d5f4ad0bf1b80f6bcf83e88f51cb6791fd8a..476448c01b65fc7cadb91d64cfcf278722c5d78e 100644 --- a/core/sysmaster/src/unit/util/unit_om.rs +++ b/core/sysmaster/src/unit/util/unit_om.rs @@ -59,6 +59,8 @@ mod noplugin { use core::unit::{SubUnit, UnitManagerObj, UnitType}; #[cfg(feature = "mount")] use mount::{self}; + #[cfg(feature = "path")] + use path::{self}; #[cfg(feature = "service")] use service::{self}; #[cfg(feature = "socket")] @@ -86,6 +88,8 @@ mod noplugin { UnitType::UnitTarget => target::__um_obj_create, #[cfg(feature = "timer")] UnitType::UnitTimer => timer::__um_obj_create, + #[cfg(feature = "path")] + UnitType::UnitPath => path::__um_obj_create, _ => { return Err(Error::Other { msg: "Component unsupported!".to_string(), @@ -114,6 +118,8 @@ mod noplugin { UnitType::UnitTarget => target::__subunit_create_with_params, #[cfg(feature = "timer")] UnitType::UnitTimer => timer::__subunit_create_with_params, + #[cfg(feature = "path")] + UnitType::UnitPath => path::__subunit_create_with_params, _ => { return Err(Error::Other { msg: "Component unsupported!".to_string(), diff --git a/factory/usr/lib/sysmaster/system/paths.target b/factory/usr/lib/sysmaster/system/paths.target new file mode 100644 index 0000000000000000000000000000000000000000..dd57493e6044a7df43d769657f7f656c0bf52191 --- /dev/null +++ b/factory/usr/lib/sysmaster/system/paths.target @@ -0,0 +1,3 @@ +[Unit] +Description=Path Units +Documentation=man sysmaster special diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml index ccf7953ccf873e40673ff7115d8c175308017b59..d62f86b582b02c2d13b1907cd41bd57967ec20a6 100644 --- a/libs/basic/Cargo.toml +++ b/libs/basic/Cargo.toml @@ -15,7 +15,7 @@ selinux = { path = "../../libs/selinux", features = [ bitflags = { version = "1.3.2", optional = true } pkg-config = { version = "0.3", optional = true } libc = { version = "0.2.140", default-features = false } -nix = { version = "0.24", default-features = false } +nix = { version = "0.24", default-features = false, features = ["dir"] } pathdiff = { version = "0.2.1", optional = true } procfs = { version = "0.12.0", default-features = false, optional = true } rand = { version = "0.8.5", optional = true } @@ -70,6 +70,7 @@ full = [ "namespace", "time", "path", + "glob", ] capability = [] @@ -123,3 +124,4 @@ unit_name = [] uuid = ["bitflags", "random"] time = [] path = [] +glob = [] diff --git a/libs/basic/src/condition.rs b/libs/basic/src/condition.rs index 7bae2727b8a2d7b00b885e3582f57650305cae6e..3c1690f0ef7869a2949ca9793273a95060fbdba5 100644 --- a/libs/basic/src/condition.rs +++ b/libs/basic/src/condition.rs @@ -194,7 +194,10 @@ impl Condition { } fn test_directory_not_empty(&self) -> i8 { - directory_is_not_empty(Path::new(&self.params)) as i8 + match directory_is_not_empty(Path::new(&self.params)) { + Ok(flag) => flag as i8, + Err(_) => false as i8, + } } fn test_file_is_executable(&self) -> i8 { diff --git a/libs/basic/src/fs.rs b/libs/basic/src/fs.rs index dcc110ae1df55281f6a2b7fbff96d6e074f97de1..201173d6d8f89aa4a359a2cdeeff4e41201b888c 100644 --- a/libs/basic/src/fs.rs +++ b/libs/basic/src/fs.rs @@ -358,15 +358,19 @@ pub fn mkdir_p_label(path: &Path, mode: u32) -> Result<()> { pub fn mkdir_parents_label() {} /// check if the given directory is not empty -pub fn directory_is_not_empty(path: &Path) -> bool { +pub fn directory_is_not_empty(path: &Path) -> Result { if path.is_file() { - return false; + return Ok(false); } let mut iter = match path.read_dir() { - Err(_) => return false, + Err(err) => { + return Err(Error::Nix { + source: nix::Error::from_i32(err.raw_os_error().unwrap_or_default()), + }) + } Ok(v) => v, }; - iter.next().is_some() + Ok(iter.next().is_some()) } /// check if the given directory is empty diff --git a/libs/basic/src/glob.rs b/libs/basic/src/glob.rs new file mode 100644 index 0000000000000000000000000000000000000000..4db18cdf3af130b0048d57c0f496b33e15a3aea9 --- /dev/null +++ b/libs/basic/src/glob.rs @@ -0,0 +1,68 @@ +// 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 globbing pathnames +//! + +use crate::error::*; +use crate::Error; +use libc::{glob, glob_t, globfree, GLOB_NOSORT}; +use nix::errno::Errno; +use std::ffi::CString; + +/// Find the first path name that matches the pattern +pub fn glob_first(path: &str) -> Result { + if path.is_empty() { + return Err(Error::Nix { + source: Errno::EINVAL, + }); + } + + let pattern = CString::new(path).unwrap(); + let mut pglob: glob_t = unsafe { std::mem::zeroed() }; + let mut first = String::new(); + + /* use GLOB_NOSORT to speed up. */ + let ret = unsafe { glob(pattern.as_ptr(), GLOB_NOSORT, None, &mut pglob) }; + if 0 != ret { + match ret { + libc::GLOB_NOSPACE => { + return Err(Error::Other { + msg: (String::from("running out of memory")), + }); + } + libc::GLOB_ABORTED => { + return Err(Error::Other { + msg: (String::from("read error")), + }); + } + libc::GLOB_NOMATCH => { + return Err(Error::Other { + msg: (String::from("no found matches")), + }); + } + _ => { + return Err(Error::Other { + msg: (String::from("Unknown error")), + }); + } + } + } + + if pglob.gl_pathc > 0 { + let ptr = unsafe { std::ffi::CStr::from_ptr(*pglob.gl_pathv.offset(0)) }; + first = ptr.to_str().unwrap().to_string(); + } + + unsafe { globfree(&mut pglob) }; + Ok(first) +} diff --git a/libs/basic/src/lib.rs b/libs/basic/src/lib.rs index c7d99d601158eef013df863c96c0a04baa575f3e..e3655f22277e3fea505b26e9bd1a43c3f5a0b421 100644 --- a/libs/basic/src/lib.rs +++ b/libs/basic/src/lib.rs @@ -33,6 +33,8 @@ pub mod exec; pub mod fd; #[cfg(feature = "fs")] pub mod fs; +#[cfg(feature = "glob")] +pub mod glob; #[cfg(feature = "host")] pub mod host; #[cfg(feature = "id128")] @@ -103,9 +105,10 @@ pub const DEFAULT_TARGET: &str = "default.target"; pub const SHUTDOWN_TARGET: &str = "shutdown.target"; /// the socketc target pub const SOCKETS_TARGET: &str = "sockets.target"; - /// the timer target pub const TIMERS_TARGET: &str = "timers.target"; +/// the path target +pub const PATHS_TARGET: &str = "paths.target"; /// early boot targets pub const SYSINIT_TARGET: &str = "sysinit.target";