From 2826aaa0ef5e24115486bff64b75e880e51fb15a Mon Sep 17 00:00:00 2001 From: huyubiao Date: Thu, 4 Jan 2024 14:57:25 +0800 Subject: [PATCH] feature: add Unit Path --- Cargo.lock | 18 + core/coms/path/Cargo.toml | 36 ++ core/coms/path/src/base.rs | 14 + core/coms/path/src/comm.rs | 196 +++++++ core/coms/path/src/config.rs | 97 ++++ core/coms/path/src/lib.rs | 51 ++ core/coms/path/src/manager.rs | 60 +++ core/coms/path/src/mng.rs | 493 ++++++++++++++++++ core/coms/path/src/rentry.rs | 209 ++++++++ core/coms/path/src/unit.rs | 374 +++++++++++++ core/coms/service/src/mng.rs | 155 +----- core/coms/socket/src/rentry.rs | 25 +- core/coms/target/src/unit.rs | 2 +- core/libcore/Cargo.toml | 1 + core/libcore/src/exec/base.rs | 19 + core/libcore/src/exec/mod.rs | 3 +- core/libcore/src/unit/deps.rs | 14 + core/libcore/src/unit/mod.rs | 4 +- core/libcore/src/unit/path_spec.rs | 277 ++++++++++ core/libcore/src/unit/umif.rs | 12 +- core/sysmaster/Cargo.toml | 7 +- core/sysmaster/src/manager/rentry.rs | 8 +- core/sysmaster/src/unit/manager.rs | 43 +- core/sysmaster/src/unit/util/unit_om.rs | 6 + factory/usr/lib/sysmaster/system/paths.target | 3 + libs/basic/Cargo.toml | 4 +- libs/basic/src/condition.rs | 5 +- libs/basic/src/fs.rs | 12 +- libs/basic/src/glob.rs | 68 +++ libs/basic/src/lib.rs | 5 +- 30 files changed, 2048 insertions(+), 173 deletions(-) create mode 100644 core/coms/path/Cargo.toml create mode 100644 core/coms/path/src/base.rs create mode 100644 core/coms/path/src/comm.rs create mode 100644 core/coms/path/src/config.rs create mode 100644 core/coms/path/src/lib.rs create mode 100644 core/coms/path/src/manager.rs create mode 100644 core/coms/path/src/mng.rs create mode 100644 core/coms/path/src/rentry.rs create mode 100644 core/coms/path/src/unit.rs create mode 100644 core/libcore/src/unit/path_spec.rs create mode 100644 factory/usr/lib/sysmaster/system/paths.target create mode 100644 libs/basic/src/glob.rs diff --git a/Cargo.lock b/Cargo.lock index fe5b58c6..47074ee4 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 00000000..9a257345 --- /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 00000000..4d569468 --- /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 00000000..ffec1054 --- /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 00000000..70f6eb85 --- /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 00000000..6e6f1f2a --- /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 00000000..92bdeefa --- /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 00000000..20695056 --- /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 00000000..04d8b4d1 --- /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 00000000..c466e345 --- /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 710507ae..4dcdc0d8 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 1de3231e..98575aed 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 4a9e8359..7a70bd57 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 3711b223..93314209 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 4f885663..dfda9c01 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 ba009f61..3cf10b6b 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 4eeedc8a..876dfef1 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 1eb565c4..a043be4b 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 00000000..bd7a6e51 --- /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 2e74caf3..1468bd9f 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 d11d236d..e61de609 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 610cecfb..3eb09437 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 245b929b..6b356f1d 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 1373d5f4..476448c0 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 00000000..dd57493e --- /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 ccf7953c..d62f86b5 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 7bae2727..3c1690f0 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 dcc110ae..201173d6 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 00000000..4db18cdf --- /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 c7d99d60..e3655f22 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"; -- Gitee