From ac13301c681cd4a4b50e4874b8f6c1f7b81e9f13 Mon Sep 17 00:00:00 2001 From: huyubiao Date: Fri, 3 Nov 2023 13:54:22 +0800 Subject: [PATCH 031/103] feature(devmaster): Add subcommands for devctl trigger --- exts/devmaster/src/bin/devctl/main.rs | 107 +++- .../src/bin/devctl/subcmds/devctl_info.rs | 5 +- .../src/bin/devctl/subcmds/devctl_trigger.rs | 482 ++++++++++++++++-- .../src/bin/devctl/subcmds/devctl_utils.rs | 15 +- exts/devmaster/src/bin/devctl/subcmds/mod.rs | 2 + exts/random_seed/src/random_seed.rs | 38 +- libs/basic/Cargo.toml | 2 + libs/basic/src/io_util.rs | 52 ++ libs/basic/src/lib.rs | 2 + libs/basic/src/random_util.rs | 70 +++ libs/basic/src/uuid.rs | 29 +- libs/device/src/device.rs | 47 +- libs/device/src/device_enumerator.rs | 151 ++++-- 13 files changed, 851 insertions(+), 151 deletions(-) create mode 100644 libs/basic/src/random_util.rs diff --git a/exts/devmaster/src/bin/devctl/main.rs b/exts/devmaster/src/bin/devctl/main.rs index 56d6d3b4..17645ee6 100644 --- a/exts/devmaster/src/bin/devctl/main.rs +++ b/exts/devmaster/src/bin/devctl/main.rs @@ -26,9 +26,8 @@ use subcmds::devctl_hwdb::subcommand_hwdb; use subcmds::devctl_info::InfoArgs; use subcmds::devctl_monitor::subcommand_monitor; use subcmds::devctl_test_builtin::subcommand_test_builtin; -use subcmds::devctl_trigger::subcommand_trigger; - -type Result = std::result::Result; +use subcmds::devctl_trigger::TriggerArgs; +use subcmds::Result; /// parse program arguments #[derive(Parser, Debug)] @@ -101,24 +100,73 @@ enum SubCmd { #[clap(display_order = 4)] Trigger { /// the kind of device action to trigger - #[clap(short, long)] + #[clap(short('c'), long)] action: Option, - /// the enumerator type, can be devices (default) or subsystems - #[clap(short, long)] + /// Type of events to trigger + #[clap(short, long, possible_values(&["devices", "subsystems", "all"]), help( + "Query device information:\n\ + devices sysfs devices (default)\n\ + subsystems sysfs subsystems and drivers\n\ + all sysfs devices, subsystems, and drivers\n") + )] r#type: Option, - /// print searched devices by enumerator + /// Print searched devices by enumerator #[clap(short, long)] verbose: bool, + /// Do not actually trigger the device events + #[clap(short('n'), long)] + dry_run: bool, + + /// Trigger devices from a matching subsystem + #[clap(short('s'), long)] + subsystem_match: Option>, + + /// Exclude devices from a matching subsystem + #[clap(short('S'), long)] + subsystem_nomatch: Option>, + + /// Trigger devices with a matching attribute + #[clap(short('a'), long)] + attr_match: Option>, + + /// Exclude devices with a matching attribute + #[clap(short('A'), long)] + attr_nomatch: Option>, + + /// Trigger devices with a matching property + #[clap(short('p'), long)] + property_match: Option>, + + /// Trigger devices with a matching tag + #[clap(short('g'), long)] + tag_match: Option>, + + /// Trigger devices with this /sys path + #[clap(short('y'), long)] + sysname_match: Option>, + + /// Trigger devices with this /dev name + #[clap(long)] + name_match: Option>, + + /// Trigger devices with this /sys path + #[clap(short('b'), long)] + parent_match: Option>, + + /// Wait for the triggered events to complete + #[clap(short('w'), long)] + settle: bool, + + /// Print synthetic uevent UUID + #[clap(long)] + uuid: bool, + /// the devices to be triggered #[clap(required = false)] devices: Vec, - - /// do not actually trigger the device events - #[clap(short('n'), long)] - dry_run: bool, }, /// Test builtin command on a device @@ -197,7 +245,7 @@ fn main() -> Result<()> { root, devices, ) - .subcommand_info() + .subcommand() } SubCmd::Monitor {} => subcommand_monitor(), SubCmd::Kill {} => subcommand_kill(), @@ -205,9 +253,40 @@ fn main() -> Result<()> { action, r#type, verbose, - devices, dry_run, - } => subcommand_trigger(devices, r#type, verbose, action, dry_run), + subsystem_match, + subsystem_nomatch, + attr_match, + attr_nomatch, + property_match, + tag_match, + sysname_match, + name_match, + parent_match, + settle, + uuid, + devices, + } => { + return TriggerArgs::new( + action, + r#type, + verbose, + dry_run, + subsystem_match, + subsystem_nomatch, + attr_match, + attr_nomatch, + property_match, + tag_match, + sysname_match, + name_match, + parent_match, + settle, + uuid, + devices, + ) + .subcommand() + } SubCmd::TestBuiltin { action, builtin, diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs index 0fa76751..e18c3898 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs @@ -13,6 +13,7 @@ //! subcommand for devctl trigger use crate::subcmds::devctl_utils; +use crate::Result; use basic::fd_util::{dot_or_dot_dot, xopendirat}; use device::{device_enumerator::DeviceEnumerator, Device}; use nix::dir::Dir; @@ -25,8 +26,6 @@ use std::os::unix::fs::MetadataExt; use std::os::unix::io::AsRawFd; use std::path::Path; -type Result = std::result::Result; - #[derive(Debug)] enum QueryType { Name, @@ -94,7 +93,7 @@ impl InfoArgs { } /// subcommand for hwdb a fake device action, then the kernel will report an uevent - pub fn subcommand_info(&self) -> Result<()> { + pub fn subcommand(&self) -> Result<()> { let mut devs = Vec::new(); let mut arg_export = false; diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs index cd425a8b..76a1712b 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs @@ -12,82 +12,482 @@ //! subcommand for devctl trigger //! -use std::{cell::RefCell, rc::Rc}; +use crate::subcmds::devctl_utils::find_device; +use crate::Result; + +use device::device_monitor::{DeviceMonitor, MonitorNetlinkGroup}; use device::{ device_enumerator::{DeviceEnumerationType, DeviceEnumerator}, - Device, DeviceAction, + DeviceAction, }; +use event::{EventState, EventType, Events, Source}; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; -/// subcommand for trigger a fake device action, then the kernel will report an uevent -pub fn subcommand_trigger( - devices: Vec, +#[derive(Debug)] +pub struct TriggerArgs { + action: Option, r#type: Option, verbose: bool, - action: Option, dry_run: bool, -) { - let mut devlist = vec![]; + subsystem_match: Option>, + subsystem_nomatch: Option>, + attr_match: Option>, + attr_nomatch: Option>, + property_match: Option>, + tag_match: Option>, + sysname_match: Option>, + name_match: Option>, + parent_match: Option>, + settle: bool, + uuid: bool, + devices: Vec, +} + +impl TriggerArgs { + #[allow(clippy::too_many_arguments)] + pub fn new( + action: Option, + r#type: Option, + verbose: bool, + dry_run: bool, + subsystem_match: Option>, + subsystem_nomatch: Option>, + attr_match: Option>, + attr_nomatch: Option>, + property_match: Option>, + tag_match: Option>, + sysname_match: Option>, + name_match: Option>, + parent_match: Option>, + settle: bool, + uuid: bool, + devices: Vec, + ) -> Self { + TriggerArgs { + action, + r#type, + verbose, + dry_run, + subsystem_match, + subsystem_nomatch, + attr_match, + attr_nomatch, + property_match, + tag_match, + sysname_match, + name_match, + parent_match, + settle, + uuid, + devices, + } + } + + /// subcommand for trigger a fake device action, then the kernel will report an uevent + pub fn subcommand(&self) -> Result<()> { + // if no device is declared, enumerate all devices or subsystems and drivers under /sys/ + let mut enumerator = DeviceEnumerator::new(); + if let Err(err) = enumerator.allow_uninitialized() { + return Err(err.get_errno()); + } + + let action = match &self.action { + Some(a) => a.parse::().unwrap(), + None => DeviceAction::Change, + }; + + if let Some(subsystems) = &self.subsystem_match { + for subsystem in subsystems { + if let Err(e) = enumerator.add_match_subsystem(subsystem, true) { + log::error!("Failed to add subsystem match {:?}", subsystem); + return Err(e.get_errno()); + } + } + } + + if let Some(subsystems) = &self.subsystem_nomatch { + for subsystem in subsystems { + if let Err(e) = enumerator.add_match_subsystem(subsystem, false) { + log::error!("Failed to add negative subsystem match {:?}", subsystem); + return Err(e.get_errno()); + } + } + } + + if let Some(attrs) = &self.attr_match { + for attr in attrs { + let (key, val) = keyval(attr); + if let Err(e) = enumerator.add_match_sysattr(&key, &val, true) { + log::error!("Failed to add sysattr match {:?}={:?}", key, val); + return Err(e.get_errno()); + } + } + } + + if let Some(attrs) = &self.attr_nomatch { + for attr in attrs { + let (key, val) = keyval(attr); + if let Err(e) = enumerator.add_match_sysattr(&key, &val, false) { + log::error!("Failed to add negative sysattr match {:?}={:?}", key, val); + return Err(e.get_errno()); + } + } + } + + if let Some(properties) = &self.property_match { + for property in properties { + let (key, val) = keyval(property); + if let Err(e) = enumerator.add_match_property(&key, &val) { + log::error!("Failed to add property match {:?}={:?}", key, val); + return Err(e.get_errno()); + } + } + } + + if let Some(tags) = &self.tag_match { + for tag in tags { + if let Err(e) = enumerator.add_match_tag(tag) { + log::error!("Failed to add tag match {:?}", tag); + return Err(e.get_errno()); + } + } + } + + if let Some(sysnames) = &self.sysname_match { + for sysname in sysnames { + if let Err(e) = enumerator.add_match_sysname(sysname, true) { + log::error!("Failed to add sysname match {:?}", sysname); + return Err(e.get_errno()); + } + } + } + + if let Some(names) = &self.name_match { + for name in names { + let dev = match find_device(name, "/dev") { + Ok(dev) => dev, + Err(e) => { + log::error!("Failed to open the device {:?}", name); + return Err(e); + } + }; + + if let Err(e) = enumerator.add_match_parent_incremental(&dev) { + log::error!("Failed to add parent match {:?} err:{:?}", name, e); + return Err(e.get_errno()); + } + } + } - let action = match action { - Some(a) => a.parse::().unwrap(), - None => DeviceAction::Change, - }; + if let Some(parents) = &self.parent_match { + for parent in parents { + let dev = match find_device(parent, "/sys") { + Ok(dev) => dev, + Err(e) => { + log::error!("Failed to open the device {:?}", parent); + return Err(e); + } + }; - // if no device is declared, enumerate all devices or subsystems and drivers under /sys/ - if devices.is_empty() { - let etype = match r#type { + if let Err(e) = enumerator.add_match_parent_incremental(&dev) { + log::error!("Failed to add parent match {:?} err:{:?}", parent, e); + return Err(e.get_errno()); + } + } + } + + for dev in &self.devices { + let device = match find_device(dev, "") { + Ok(d) => d, + Err(e) => { + log::error!("Failed to open the device: {:?} err:{:?}", dev, e); + return Err(e); + } + }; + if let Err(err) = enumerator.add_match_parent_incremental(&device) { + return Err(err.get_errno()); + } + } + + let etype = match &self.r#type { Some(t) => { if t == "devices" { DeviceEnumerationType::Devices } else if t == "subsystems" { DeviceEnumerationType::Subsystems + } else if t == "all" { + DeviceEnumerationType::All } else { log::error!("invalid events type{}", t); - return; + return Err(nix::Error::EINVAL); } } None => DeviceEnumerationType::Devices, }; - let mut enumerator = DeviceEnumerator::new(); enumerator.set_enumerator_type(etype); - for device in enumerator.iter() { - devlist.push(device); + + let settle_path_or_ids = self.exec_list(&mut enumerator, action)?; + + let events = Events::new().unwrap(); + if self.settle { + let monitor = Rc::new(TriggerMonitor::new( + DeviceMonitor::new(MonitorNetlinkGroup::Userspace, None), + settle_path_or_ids.clone(), + self.verbose, + self.uuid, + )); + events.add_source(monitor.clone()).unwrap(); + events.set_enabled(monitor, EventState::On).unwrap(); } - } else { - for d in devices { - match Device::from_path(&d) { - Ok(dev) => { - devlist.push(Rc::new(RefCell::new(dev))); - } + + if !settle_path_or_ids.is_empty() { + if let Err(e) = events.rloop() { + log::error!("Event loop failed err:{:?}", e); + return Err(nix::Error::EINVAL); + } + } + + Ok(()) + } + + fn exec_list( + &self, + enumerator: &mut DeviceEnumerator, + action: DeviceAction, + ) -> Result> { + let mut uuid_supported = -1; + let mut uuids = HashSet::new(); + let mut ret = Ok(HashSet::::new()); + for device in enumerator.iter() { + let syspath = match device.borrow().get_syspath() { + Ok(syspath) => syspath, + Err(_) => continue, + }; + if self.verbose { + println!("{}", syspath); + } + if self.dry_run { + continue; + } + + let id = match device + .borrow() + .trigger_with_uuid(action, (self.uuid || self.settle) && uuid_supported != 0) + { + Ok(id) => id, Err(e) => { - eprintln!("Invalid device path '{}': {}", d, e); + if e.get_errno() == nix::errno::Errno::EINVAL + && !self.uuid + && self.settle + && uuid_supported < 0 + { + /* If we specified a UUID because of the settling logic, and we got EINVAL this might + * be caused by an old kernel which doesn't know the UUID logic (pre-4.13). Let's try + * if it works without the UUID logic then. */ + if let Err(e) = device.borrow().trigger(action) { + if e.get_errno() != nix::Error::EINVAL { + /* dropping the uuid stuff changed the return code, + * hence don't bother next time */ + uuid_supported = 0; + } + } + None + } else { + if ![nix::Error::ENOENT, nix::Error::ENODEV].contains(&e.get_errno()) { + eprintln!("Failed to trigger {:?}: {:?}", syspath, e); + if ret.is_ok() { + ret = Err(e.get_errno()); + } + } else { + println!("Ignore to trigger {:?}: {:?}", syspath, e); + } + + if [nix::Error::EACCES, nix::Error::EROFS].contains(&e.get_errno()) { + /* Inovoked by unprivileged user, or read only filesystem. Return earlier. */ + return Err(e.get_errno()); + } + continue; + } + } + }; + + if uuid_supported < 0 { + uuid_supported = 1; + } + + /* If the user asked for it, write event UUID to stdout */ + if self.uuid { + if let Some(uuid) = &id { + println!("{}", uuid.to_string()); } } + + if self.settle { + if uuid_supported != 0 { + if let Some(uuid) = id { + uuids.insert(uuid.to_string()); + } + } else { + uuids.insert(syspath); + } + } + } + + if let Err(err) = ret { + return Err(err); } + + Ok(uuids) } +} - for d in devlist { - if !dry_run { - if let Err(e) = d.borrow().trigger(action) { - if ![nix::Error::ENOENT, nix::Error::ENODEV].contains(&e.get_errno()) { - eprintln!( - "Failed to trigger '{}': {}", - d.borrow().get_syspath().unwrap_or_default(), - e +fn keyval(buf: &str) -> (String, String) { + let mut key = buf.to_string(); + let mut val = String::new(); + + if let Some(pos) = buf.rfind('=') { + let (left, right) = buf.split_at(pos); + let right = &right[1..]; + key = left.to_string(); + val = right.to_string(); + } + + (key, val) +} + +/// trigger monitor +#[derive(Debug)] +struct TriggerMonitor { + device_monitor: DeviceMonitor, + settle_path_or_ids: RefCell>, + verbose: bool, + uuid: bool, +} + +/// public methods +impl TriggerMonitor { + /// create a monitor instance for monitoring trigger + pub fn new( + device_monitor: DeviceMonitor, + settle_path_or_ids: HashSet, + verbose: bool, + uuid: bool, + ) -> TriggerMonitor { + TriggerMonitor { + device_monitor, + settle_path_or_ids: RefCell::new(settle_path_or_ids), + verbose, + uuid, + } + } +} + +impl Source for TriggerMonitor { + /// socket fd + fn fd(&self) -> RawFd { + self.device_monitor.fd() + } + + /// event type + fn event_type(&self) -> EventType { + EventType::Io + } + + /// epoll type + fn epoll_event(&self) -> u32 { + (libc::EPOLLIN) as u32 + } + + /// priority of event source + fn priority(&self) -> i8 { + 0i8 + } + + /// receive device from socket and remove path or uuid from settle_path_or_ids + fn dispatch(&self, event: &Events) -> i32 { + let device = match self.device_monitor.receive_device() { + Ok(device) => device, + Err(_) => { + return 0; + } + }; + + let syspath = match device.get_syspath() { + Ok(syspath) => syspath, + Err(e) => { + log::error!("Failed to get syspath of device event, ignoring:{:?}", e); + return 0; + } + }; + + let id = device.get_trigger_uuid(); + match &id { + Ok(Some(id)) => { + if !self.settle_path_or_ids.borrow_mut().remove(&id.to_string()) { + log::debug!( + "Got uevent not matching expected UUID, ignoring. {:?}", + device.get_syspath() ); - } else { - println!( - "Ignore to trigger '{}': {}", - d.borrow().get_syspath().unwrap_or_default(), - e + return 0; + } + } + _ => { + let mut saved = self.settle_path_or_ids.borrow_mut().remove(&syspath); + if !saved { + /* When the device is renamed, the new name is broadcast, and the old name is saved + * in INTERFACE_OLD. + * + * TODO: remove support for INTERFACE_OLD when kernel baseline is bumped to 4.13 or + * higher. + */ + if let Ok(old_sysname) = device.get_property_value("INTERFACE_OLD") { + let dir = match Path::new(&syspath).parent() { + Some(dir) => dir.to_str().unwrap(), + None => { + log::error!( + "Failed to extract directory from {:?}, ignoring", + syspath + ); + return 0; + } + }; + let old_syspath = dir.to_string() + "/" + &old_sysname; + saved = self.settle_path_or_ids.borrow_mut().remove(&old_syspath); + } + } + + if !saved { + log::debug!( + "Got uevent for unexpected device, ignoring. {:?}", + device.get_syspath() ); + return 0; } } } - if verbose { - println!("{}", d.borrow().get_syspath().unwrap_or_default()); + + if self.verbose { + println!("settle {}", syspath); } + + if self.uuid { + println!("settle {}", id.unwrap().unwrap().to_string()); + } + + if self.settle_path_or_ids.borrow().is_empty() { + event.set_exit(); + } + + 0 + } + + /// token of event source + fn token(&self) -> u64 { + let data: u64 = unsafe { std::mem::transmute(self) }; + data } } diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs index 63b7a98e..ecaab3a2 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs @@ -1,4 +1,5 @@ use device::Device; +use std::path::PathBuf; type Result = std::result::Result; @@ -11,14 +12,22 @@ pub fn find_device(id: &str, prefix: &str) -> Result { return Ok(device); } - if !prefix.is_empty() && !id.starts_with(prefix) { - let path = prefix.to_string() + id; + let mut path = PathBuf::from(id); - if let Ok(device) = Device::from_path(&path) { + if !prefix.is_empty() && !id.starts_with(prefix) { + path = PathBuf::from(prefix.to_string() + "/" + id) + .canonicalize() + .unwrap(); + if let Ok(device) = Device::from_path(path.to_str().unwrap()) { return Ok(device); } } + /* if a path is provided, then it cannot be a unit name. Let's return earlier. */ + if path.to_str().unwrap().contains('/') { + return Err(nix::Error::ENODEV); + } + /* Check if the argument looks like a device unit name. */ find_device_from_unit(id) } diff --git a/exts/devmaster/src/bin/devctl/subcmds/mod.rs b/exts/devmaster/src/bin/devctl/subcmds/mod.rs index 3d79a88d..e4289028 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/mod.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/mod.rs @@ -19,3 +19,5 @@ pub(crate) mod devctl_monitor; pub(crate) mod devctl_test_builtin; pub(crate) mod devctl_trigger; pub(self) mod devctl_utils; + +pub(crate) type Result = std::result::Result; diff --git a/exts/random_seed/src/random_seed.rs b/exts/random_seed/src/random_seed.rs index 009be2ec..74846a6f 100644 --- a/exts/random_seed/src/random_seed.rs +++ b/exts/random_seed/src/random_seed.rs @@ -10,6 +10,7 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +use basic::io_util::loop_read; use nix::ioctl_write_ptr; use std::alloc::{alloc, dealloc, Layout}; use std::ffi::CString; @@ -17,7 +18,7 @@ use std::path::{Path, PathBuf}; use std::{env, mem, str}; use std::{ fs::{self, read_link, File}, - io::{self, Read, Seek, Write}, + io::{self, Seek, Write}, os::unix::prelude::AsRawFd, }; @@ -173,26 +174,6 @@ fn fsync_full(file: &mut File) -> bool { true } -fn loop_read(file: &mut File, buf: &mut [u8]) -> Result { - let size = buf.len(); - let mut pos = 0; - while pos < size { - let read_size = match file.read(&mut buf[pos..]) { - Ok(size) => size, - Err(err) => { - println!("{}", err); - return Err(()); - } - }; - - pos += read_size; - if read_size == 0 { - return Ok(pos); - } - } - Ok(pos) -} - fn sd_id128_from_string(buf: &[u8]) -> Result<[u8; 16], ()> { let mut bytes = [0; 16]; @@ -603,27 +584,12 @@ pub fn run(arg: &str) -> Result<(), String> { #[cfg(test)] mod test { - use std::io::Seek; - use super::*; fn is_root() -> bool { let uid = unsafe { libc::geteuid() }; uid == 0 } - #[test] - fn loop_read_test() { - let mut file = fs::OpenOptions::new() - .read(true) - .open("/etc/machine-id") - .unwrap(); - let mut buf = [0; 38]; - assert_eq!(33, loop_read(&mut file, &mut buf).unwrap()); - file.rewind().unwrap(); - let mut buf = [0; 20]; - assert_eq!(20, loop_read(&mut file, &mut buf).unwrap()); - } - #[test] fn sd_id128_from_string_test() { let buf = b"e57446f87c3f4f978a7eca30ff7197d3"; diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml index e18db5c7..ebcb7801 100644 --- a/libs/basic/Cargo.toml +++ b/libs/basic/Cargo.toml @@ -63,6 +63,7 @@ full = [ "strbuf", "argv", "exec_util", + "random", ] capability = [] @@ -111,3 +112,4 @@ murmurhash2 = [] strbuf = [] argv = [] exec_util = [] +random = [] diff --git a/libs/basic/src/io_util.rs b/libs/basic/src/io_util.rs index b90d3a9b..d443976f 100644 --- a/libs/basic/src/io_util.rs +++ b/libs/basic/src/io_util.rs @@ -17,6 +17,8 @@ use nix::{ poll::{self, PollFd, PollFlags}, sys::{signal::SigSet, time::TimeSpec}, }; +use std::fs::File; +use std::io::Read; use std::os::unix::prelude::RawFd; fn ppoll_timeout(fds: &mut [PollFd], timeout: Option) -> Result { @@ -58,3 +60,53 @@ pub fn wait_for_events(fd: RawFd, event: PollFlags, time_out: i64) -> Result Result { + let size = buf.len(); + let mut pos = 0; + while pos < size { + let read_size = file.read(&mut buf[pos..]).context(IoSnafu)?; + + pos += read_size; + if read_size == 0 { + return Ok(pos); + } + } + Ok(pos) +} + +/// Read data from file to buf. If buf is full, succeeds, otherwise fails. +pub fn loop_read_exact(file: &mut File, buf: &mut [u8]) -> Result<()> { + let n = loop_read(file, buf)?; + + if n != buf.len() { + return Err(Error::Nix { + source: nix::errno::Errno::EIO, + }); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use std::io::Seek; + + #[test] + fn loop_read_test() { + let mut file = std::fs::OpenOptions::new() + .read(true) + .open("/etc/machine-id") + .unwrap(); + let mut buf = [0; 38]; + assert_eq!(33, loop_read(&mut file, &mut buf).unwrap()); + file.rewind().unwrap(); + let mut buf = [0; 20]; + assert_eq!(20, loop_read(&mut file, &mut buf).unwrap()); + + let mut buf = [0; 10]; + loop_read_exact(&mut file, &mut buf).unwrap(); + } +} diff --git a/libs/basic/src/lib.rs b/libs/basic/src/lib.rs index 0f40793d..ead31ea2 100644 --- a/libs/basic/src/lib.rs +++ b/libs/basic/src/lib.rs @@ -55,6 +55,8 @@ pub mod os_release; pub mod parse; #[cfg(feature = "process")] pub mod process; +#[cfg(feature = "random")] +pub mod random_util; #[cfg(feature = "rlimit")] pub mod rlimit; #[cfg(feature = "security")] diff --git a/libs/basic/src/random_util.rs b/libs/basic/src/random_util.rs new file mode 100644 index 00000000..831bf8ba --- /dev/null +++ b/libs/basic/src/random_util.rs @@ -0,0 +1,70 @@ +// 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. + +//! + +/// Get random data from getrandom() or '/dev/urandom' +pub fn random_bytes(data: &mut [u8]) { + let mut have_grndinsecure = true; + + if data.is_empty() { + return; + } + + let mut l: usize = 0; + loop { + let flag = match have_grndinsecure { + true => libc::GRND_INSECURE, + false => libc::GRND_NONBLOCK, + }; + let size = unsafe { + libc::getrandom( + data[l..].as_mut_ptr() as *mut libc::c_void, + data.len() - l, + flag, + ) + }; + + if size > 0 { + l += size as usize; + if l as usize == data.len() { + /* Done reading, success. */ + return; + } + continue; + } else if size == 0 + || crate::error::errno_is_not_supported( + nix::errno::Errno::from_i32(nix::errno::errno()), + ) + || nix::errno::errno() == libc::EAGAIN && !have_grndinsecure + { + /* Weird or No syscall or Will block, but no GRND_INSECURE. Fallback to /dev/urandom. */ + break; + } else if nix::errno::errno() == libc::EINVAL && have_grndinsecure { + /* No GRND_INSECURE; fallback to GRND_NONBLOCK. */ + have_grndinsecure = false; + continue; + } + + /* Unexpected, so just give up and fallback to /dev/urandom. */ + break; + } + + match std::fs::OpenOptions::new().read(true).open("/dev/urandom") { + Err(err) => { + log::error!("Failed to open /dev/urandom, err:{}", err); + } + Ok(mut file) => { + let _ = crate::io_util::loop_read_exact(&mut file, data); + } + }; +} diff --git a/libs/basic/src/uuid.rs b/libs/basic/src/uuid.rs index aaea8746..c1bd8030 100644 --- a/libs/basic/src/uuid.rs +++ b/libs/basic/src/uuid.rs @@ -46,7 +46,7 @@ pub const GPT_ROOT_NATIVE: Uuid = Uuid([ ]); /// uuid -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Clone, Hash)] pub struct Uuid(pub(super) [u8; 16]); fn unhexchar(c: u8) -> Result { @@ -167,6 +167,33 @@ impl Uuid { } } +/// get random uuid +pub fn randomize() -> Result { + let mut id = Uuid::new(); + + crate::random_util::random_bytes(&mut id.0); + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones. */ + + Ok(id128_make_v4_uuid(id)) +} + +fn id128_make_v4_uuid(uuid: Uuid) -> Uuid { + /* Stolen from generate_random_uuid() of drivers/char/random.c + * in the kernel sources */ + + /* Set UUID version to 4 --- truly random generation */ + let mut id = uuid; + id.0[6] = (id.0[6] & 0x0F) | 0x40; + + /* Set the UUID variant to DCE */ + id.0[8] = (id.0[8] & 0x3F) | 0x80; + + id +} + #[cfg(test)] mod tests { use super::*; diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs index 5a95e0f5..d6a4dc07 100644 --- a/libs/device/src/device.rs +++ b/libs/device/src/device.rs @@ -17,6 +17,7 @@ use crate::utils::readlink_value; use crate::{error::*, DeviceAction}; use basic::fs_util::{chmod, open_temporary, touch_file}; use basic::parse::{device_path_parse_devnum, parse_devnum, parse_ifindex}; +use basic::uuid::{randomize, Uuid}; use libc::{ dev_t, faccessat, gid_t, mode_t, uid_t, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IRUSR, S_IWUSR, @@ -1009,8 +1010,21 @@ impl Device { } /// get the trigger uuid of the device - pub fn get_trigger_uuid(&self) -> Result<[u8; 8], Error> { - todo!() + pub fn get_trigger_uuid(&self) -> Result, Error> { + /* Retrieves the UUID attached to a uevent when triggering it from userspace via + * trigger_with_uuid() or an equivalent interface. Returns ENOENT if the record is not + * caused by a synthetic event and ENODATA if it was but no UUID was specified */ + let s = self.get_property_value("SYNTH_UUID")?; + + /* SYNTH_UUID=0 is set whenever a device is triggered by userspace without specifying a UUID */ + if s == "0" { + return Err(Error::Nix { + msg: format!(""), + source: nix::errno::Errno::ENODATA, + }); + } + + Ok(Uuid::from_string(&s)) } /// get the value of specific device sysattr @@ -1103,8 +1117,33 @@ impl Device { } /// trigger with uuid - pub fn trigger_with_uuid(&self, _action: DeviceAction) -> Result<[u8; 8], Error> { - todo!() + pub fn trigger_with_uuid( + &self, + action: DeviceAction, + need_uuid: bool, + ) -> Result, Error> { + if !need_uuid { + self.trigger(action)?; + return Ok(None); + } + + let s = format!("{}", action); + + let id = match randomize() { + Ok(id) => id, + Err(e) => { + return Err(Error::Nix { + msg: "Failed to randomize".to_string(), + source: e, + }) + } + }; + + let j = s + " " + &id.to_string(); + + self.set_sysattr_value("uevent", Some(&j))?; + + Ok(Some(id)) } /// open device diff --git a/libs/device/src/device_enumerator.rs b/libs/device/src/device_enumerator.rs index 38956cf5..96cac2d9 100644 --- a/libs/device/src/device_enumerator.rs +++ b/libs/device/src/device_enumerator.rs @@ -69,10 +69,10 @@ pub struct DeviceEnumerator { /// match sysattr /// key: sysattr, value: match value - pub(crate) match_sysattr: RefCell>>, + pub(crate) match_sysattr: RefCell>, /// do not match sysattr /// key: sysattr, value: match value - pub(crate) not_match_sysattr: RefCell>>, + pub(crate) not_match_sysattr: RefCell>, /// match property /// key: property, value: match value @@ -240,46 +240,14 @@ impl DeviceEnumerator { ) -> Result<(), Error> { match whether_match { true => { - let sysattr_is_none = self.match_sysattr.borrow().get(sysattr).is_none(); - - if sysattr_is_none { - self.match_sysattr - .borrow_mut() - .insert(sysattr.to_string(), HashSet::new()); - - self.match_sysattr - .borrow_mut() - .get_mut(sysattr) - .unwrap() - .insert(value.to_string()); - } else { - self.match_sysattr - .borrow_mut() - .get_mut(sysattr) - .unwrap() - .insert(value.to_string()); - } + self.match_sysattr + .borrow_mut() + .insert(sysattr.to_string(), value.to_string()); } false => { - let not_match_sysattr_is_none = - self.not_match_sysattr.borrow().get(sysattr).is_none(); - - if not_match_sysattr_is_none { - self.not_match_sysattr - .borrow_mut() - .insert(sysattr.to_string(), HashSet::new()); - self.not_match_sysattr - .borrow_mut() - .get_mut(sysattr) - .unwrap() - .insert(value.to_string()); - } else { - self.not_match_sysattr - .borrow_mut() - .get_mut(sysattr) - .unwrap() - .insert(value.to_string()); - } + self.not_match_sysattr + .borrow_mut() + .insert(sysattr.to_string(), value.to_string()); } }; @@ -548,12 +516,20 @@ impl DeviceEnumerator { /// check whether the value of specific sysattr of a device matches pub(crate) fn match_sysattr_value( &self, - _device: &Device, - _sysattr: &str, - _patterns: &HashSet, + device: &Device, + sysattr: &str, + patterns: &str, ) -> Result { - todo!("Device::get_sysattr_value has not been implemented."); - // Ok(false) + let value = match device.get_sysattr_value(sysattr) { + Ok(value) => value, + Err(_) => return Ok(false), + }; + + if patterns.is_empty() { + return Ok(true); + } + + self.pattern_match(patterns, &value) } /// check whether a device matches conditions according to flags @@ -922,9 +898,77 @@ impl DeviceEnumerator { } /// scan devices for a single tag - pub(crate) fn scan_devices_tag(&self, _tag: &str) -> Result<(), Error> { - todo!("scan_devices_tag has not been implemented."); - // Ok(()) + pub(crate) fn scan_devices_tag(&self, tag: &str) -> Result<(), Error> { + let path = Path::new("/run/devmaster/tags/").join(tag); + let dir = std::fs::read_dir(path); + let tag_dir = match dir { + Ok(d) => d, + Err(e) => { + if e.raw_os_error().unwrap_or_default() == libc::ENOENT { + return Ok(()); + } else { + return Err(Error::Nix { + msg: format!("scan_dir failed: can't read directory '{}'", tag), + source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), + }); + } + } + }; + + /* TODO: filter away subsystems? */ + + let mut ret = Ok(()); + for entry in tag_dir { + let entry = match entry { + Ok(e) => e, + Err(e) => { + ret = Err(Error::Nix { + msg: format!( + "scan_dir failed: can't read entries from directory '{}'", + tag + ), + source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), + }); + continue; + } + }; + + let file_name = entry.file_name().to_str().unwrap().to_string(); + if file_name.contains('.') { + continue; + } + + let mut device = match Device::from_device_id(&file_name) { + Ok(device) => device, + Err(e) => { + if e.get_errno() != nix::errno::Errno::ENODEV { + /* this is necessarily racy, so ignore missing devices */ + ret = Err(e); + } + continue; + } + }; + + /* Generated from tag, hence not necessary to check tag again. */ + match self.test_matches(&mut device, MatchFlag::ALL & (!MatchFlag::TAG)) { + Ok(flag) => { + if !flag { + continue; + } + } + Err(e) => { + ret = Err(e); + continue; + } + } + + if let Err(e) = self.add_device(Rc::new(RefCell::new(device))) { + ret = Err(e); + continue; + } + } + + ret } /// scan devices tags @@ -944,7 +988,16 @@ impl DeviceEnumerator { /// parent add child pub(crate) fn parent_add_child(&mut self, path: &str, flags: MatchFlag) -> Result { - let device = Rc::new(RefCell::new(Device::from_syspath(path, true)?)); + let device = match Device::from_syspath(path, true) { + Ok(dev) => Rc::new(RefCell::new(dev)), + Err(err) => { + if err.get_errno() == nix::errno::Errno::ENODEV { + /* this is necessarily racy, so ignore missing devices */ + return Ok(false); + } + return Err(err); + } + }; if !self.test_matches(&mut device.borrow_mut(), flags)? { return Ok(false); -- 2.33.0