From f5e7fd3a5a0d8efa982dfd0291d0c9d00bd7c7dd Mon Sep 17 00:00:00 2001 From: huyubiao Date: Mon, 20 Nov 2023 04:21:22 +0800 Subject: [PATCH] feature(devmaster): Add subcommands for devctl monitor -p --property Print the event properties -k --kernel Print kernel uevents -u --udev Print udev events -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem -t --tag-match=TAG Filter events by tag next_datagram_size_fd: get the size of data in fd --- Cargo.lock | 1 + exts/devmaster/Cargo.toml | 1 + exts/devmaster/src/bin/devctl/main.rs | 59 ++++- .../src/bin/devctl/subcmds/devctl_monitor.rs | 229 +++++++++++++++--- .../src/bin/devctl/subcmds/devctl_trigger.rs | 5 +- .../src/lib/framework/uevent_monitor.rs | 5 +- libs/basic/Cargo.toml | 1 + libs/basic/src/error.rs | 12 + libs/basic/src/socket_util.rs | 69 ++++++ libs/basic/src/string.rs | 97 ++++++++ libs/device/Cargo.toml | 2 + libs/device/src/device.rs | 99 ++++++++ libs/device/src/device_enumerator.rs | 123 ++-------- libs/device/src/device_monitor.rs | 202 ++++++++++++++- 14 files changed, 751 insertions(+), 154 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e22becd..af2996c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,7 @@ version = "0.5.1" dependencies = [ "bitflags", "constants", + "fnmatch-sys", "libc", "log 0.5.1", "nix 0.24.3", diff --git a/exts/devmaster/Cargo.toml b/exts/devmaster/Cargo.toml index 4a3a8176..a5946d0a 100644 --- a/exts/devmaster/Cargo.toml +++ b/exts/devmaster/Cargo.toml @@ -64,6 +64,7 @@ nix = { version = "0.24", default-features = false, features = [ "net", "poll", "resource", + "time", ] } rtnetlink = "0.8.1" serde = { version = "1.0.130", default-features = false } diff --git a/exts/devmaster/src/bin/devctl/main.rs b/exts/devmaster/src/bin/devctl/main.rs index 1340a467..c167f20d 100644 --- a/exts/devmaster/src/bin/devctl/main.rs +++ b/exts/devmaster/src/bin/devctl/main.rs @@ -25,7 +25,7 @@ use log::Level; use std::{io::Write, os::unix::net::UnixStream}; use subcmds::devctl_hwdb::subcommand_hwdb; use subcmds::devctl_info::InfoArgs; -use subcmds::devctl_monitor::subcommand_monitor; +use subcmds::devctl_monitor::MonitorArgs; use subcmds::devctl_settle::SettleArgs; use subcmds::devctl_test_builtin::subcommand_test_builtin; use subcmds::devctl_trigger::TriggerArgs; @@ -92,7 +92,35 @@ enum SubCmd { /// Monitor device events from kernel and userspace #[clap(display_order = 2)] - Monitor {}, + Monitor { + /// Print the event properties + #[clap(short('p'), long)] + property: bool, + + /// Print the event properties (alias for -p) + #[clap(short('e'), long)] + environment: bool, + + /// Print kernel uevents + #[clap(short('k'), long)] + kernel: bool, + + /// Print events broadcasted from devmaster other applications in userspace + #[clap(short('u'), long)] + userspace: bool, + + /// Filter events by subsystem + #[clap(short('s'), long)] + subsystem_match: Option>, + + /// Filter events by tag + #[clap(short('t'), long)] + tag_match: Option>, + + /// other args + #[clap(required = false)] + _other: Vec, + }, /// Kill all devmaster workers #[clap(display_order = 3)] @@ -184,7 +212,7 @@ enum SubCmd { /// other args #[clap(required = false)] - other: Vec, + _other: Vec, }, /// Test builtin command on a device @@ -280,7 +308,26 @@ fn main() -> Result<()> { ) .subcommand() } - SubCmd::Monitor {} => subcommand_monitor(), + SubCmd::Monitor { + property, + environment, + kernel, + userspace, + subsystem_match, + tag_match, + _other, + } => { + return MonitorArgs::new( + property, + environment, + kernel, + userspace, + subsystem_match, + tag_match, + _other, + ) + .subcommand() + } SubCmd::Kill {} => subcommand_kill(), SubCmd::Trigger { action, @@ -323,9 +370,9 @@ fn main() -> Result<()> { SubCmd::Settle { timeout, exit_if_exists, - other, + _other, } => { - return SettleArgs::new(timeout, exit_if_exists, other).subcommand(); + return SettleArgs::new(timeout, exit_if_exists, _other).subcommand(); } SubCmd::TestBuiltin { action, diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_monitor.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_monitor.rs index 44ba6640..df6768ab 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/devctl_monitor.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_monitor.rs @@ -12,10 +12,14 @@ //! subcommand for devctl monitor //! + +use crate::Result; use basic::socket_util::set_receive_buffer; use device::{device_monitor::DeviceMonitor, device_monitor::MonitorNetlinkGroup}; use event::{EventState, EventType, Events, Source}; use nix::errno::Errno; +use nix::sys::signal::Signal; +use std::collections::{HashMap, HashSet}; use std::{os::unix::prelude::RawFd, rc::Rc}; /// wrapper of DeviceMonitor @@ -25,6 +29,8 @@ struct DevctlMonitorX { /// prefix in log prefix: String, + + show_property: bool, } impl Source for DevctlMonitorX { @@ -51,7 +57,10 @@ impl Source for DevctlMonitorX { /// print device messages from kernel and userspace fn dispatch(&self, _e: &Events) -> i32 { let device = match self.device_monitor.receive_device() { - Ok(ret) => ret, + Ok(ret) => match ret { + Some(device) => device, + None => return 0, + }, Err(e) => match e { device::error::Error::Nix { msg: _, @@ -69,13 +78,21 @@ impl Source for DevctlMonitorX { }, }; + let ts = nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC).unwrap(); println!( - "{} >> {} {} ({})", + "{} [{}] {} {} ({})", self.prefix, + ts.to_string(), device.get_action().unwrap(), device.get_devpath().unwrap(), device.get_subsystem().unwrap() ); + if self.show_property { + for properties in &device.property_iter() { + println!("{}={}", properties.0, properties.1); + } + println!(); + } 0 } @@ -86,41 +103,193 @@ impl Source for DevctlMonitorX { } } -/// subcommand for monitoring device messages from kernel and userspace -pub fn subcommand_monitor() { - println!( - "start monitoring device events: -KERNEL - the kernel uevent -USERSPACE - broadcasted by devmaster after successful process on device -", - ); +struct DevctlMonitorSignal {} + +impl DevctlMonitorSignal { + fn new() -> DevctlMonitorSignal { + DevctlMonitorSignal {} + } +} + +impl Source for DevctlMonitorSignal { + /// monitor socket fd + fn fd(&self) -> RawFd { + 0 + } + + /// The signal type needs to specify the signal to listen to + fn signals(&self) -> Vec { + vec![Signal::SIGINT, Signal::SIGTERM] + } + + /// event type + fn event_type(&self) -> EventType { + EventType::Signal + } + + /// epoll type + fn epoll_event(&self) -> u32 { + (libc::EPOLLIN) as u32 + } + + /// event source priority + fn priority(&self) -> i8 { + 0i8 + } + + /// print device messages from kernel and userspace + fn dispatch(&self, event: &Events) -> i32 { + event.set_exit(); + 0 + } - let kernel_monitor = Rc::new(DevctlMonitorX { - device_monitor: DeviceMonitor::new(MonitorNetlinkGroup::Kernel, None), - prefix: "KERNEL []".to_string(), - }); + /// source token + fn token(&self) -> u64 { + let data: u64 = unsafe { std::mem::transmute(self) }; + data + } +} - let userspace_monitor = Rc::new(DevctlMonitorX { - device_monitor: DeviceMonitor::new(MonitorNetlinkGroup::Userspace, None), - prefix: "USERSPACE []".to_string(), - }); +pub struct MonitorArgs { + property: bool, + environment: bool, + kernel: bool, + userspace: bool, + subsystem_match: Option>, + tag_match: Option>, + _other: Vec, +} - if let Err(errno) = set_receive_buffer(kernel_monitor.fd(), 1024 * 1024 * 128) { - log::error!("Failed to set receive buffer forcely ({:?})", errno); +impl MonitorArgs { + pub fn new( + property: bool, + environment: bool, + kernel: bool, + userspace: bool, + subsystem_match: Option>, + tag_match: Option>, + _other: Vec, + ) -> Self { + MonitorArgs { + property, + environment, + kernel, + userspace, + subsystem_match, + tag_match, + _other, + } } - if let Err(errno) = set_receive_buffer(userspace_monitor.fd(), 1024 * 1024 * 128) { - log::error!("Failed to set receive buffer forcely ({:?})", errno); + /// subcommand for monitoring device messages from kernel and userspace + pub fn subcommand(&self) -> Result<()> { + let events = Events::new().unwrap(); + + let mut print_kernel = false; + let mut print_userspace = false; + + if self.kernel { + print_kernel = true; + } else if self.userspace { + print_userspace = true; + } else { + print_kernel = true; + print_userspace = true; + } + + let mut subsystem_filter: HashMap = HashMap::new(); + if let Some(subsystem_devtypes) = &self.subsystem_match { + for subsystem_devtype in subsystem_devtypes { + if let Some(pos) = subsystem_devtype.find('/') { + let devtype = subsystem_devtype[pos + 1..].to_string(); + let subsystem = subsystem_devtype[..pos].to_string(); + subsystem_filter.insert(subsystem, devtype); + } else { + subsystem_filter.insert(subsystem_devtype.to_string(), "".to_string()); + } + } + } + + let mut tag_filter: HashSet = HashSet::new(); + if let Some(tags) = &self.tag_match { + for tag in tags { + tag_filter.insert(tag.to_string()); + } + } + + let signal = Rc::new(DevctlMonitorSignal::new()); + events.add_source(signal.clone()).unwrap(); + events.set_enabled(signal, EventState::OneShot).unwrap(); + + println!("monitor will print the received events for:"); + + if print_kernel { + self.setup_monitor( + MonitorNetlinkGroup::Kernel, + "KERNEL".to_string(), + &events, + subsystem_filter.clone(), + tag_filter.clone(), + )?; + println!("KERNEL - the kernel uevent"); + } + if print_userspace { + self.setup_monitor( + MonitorNetlinkGroup::Userspace, + "USERSPACE".to_string(), + &events, + subsystem_filter, + tag_filter, + )?; + println!("USERSPACE - broadcasted by devmaster after successful process on device"); + } + println!(); + + events.rloop().unwrap(); + Ok(()) } - let events = Events::new().unwrap(); + fn setup_monitor( + &self, + sender: MonitorNetlinkGroup, + prefix: String, + events: &Events, + subsystem_filter: HashMap, + tag_filter: HashSet, + ) -> Result<()> { + let mut device_monitor = DeviceMonitor::new(sender, None); + for (subsystem, devtype) in &subsystem_filter { + if let Err(err) = device_monitor.filter_add_match_subsystem_devtype(subsystem, devtype) + { + log::error!( + "Failed to apply subsystem filter subsystem:{:?} devtype:{:?}", + subsystem, + devtype + ); + return Err(err.get_errno()); + } + } - events.add_source(kernel_monitor.clone()).unwrap(); - events.add_source(userspace_monitor.clone()).unwrap(); - events.set_enabled(kernel_monitor, EventState::On).unwrap(); - events - .set_enabled(userspace_monitor, EventState::On) - .unwrap(); + for tag in &tag_filter { + if let Err(err) = device_monitor.filter_add_match_tag(tag) { + log::error!("Failed to apply tag filter {:?}", tag); + return Err(err.get_errno()); + } + } - events.rloop().unwrap(); + let monitor = Rc::new(DevctlMonitorX { + device_monitor, + prefix, + show_property: self.property || self.environment, + }); + if let Err(err) = set_receive_buffer(monitor.fd(), 1024 * 1024 * 128) { + log::error!("Failed to set receive buffer forcely ({:?})", err); + return Err(nix::errno::from_i32(err.get_errno())); + } + + events.add_source(monitor.clone()).unwrap(); + events.set_enabled(monitor, EventState::On).unwrap(); + + Ok(()) + } } diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs index 76a1712b..93a57cb4 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_trigger.rs @@ -410,7 +410,10 @@ impl Source for TriggerMonitor { /// 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, + Ok(ret) => match ret { + Some(device) => device, + None => return 0, + }, Err(_) => { return 0; } diff --git a/exts/devmaster/src/lib/framework/uevent_monitor.rs b/exts/devmaster/src/lib/framework/uevent_monitor.rs index 30bbd570..0add91f6 100644 --- a/exts/devmaster/src/lib/framework/uevent_monitor.rs +++ b/exts/devmaster/src/lib/framework/uevent_monitor.rs @@ -67,7 +67,10 @@ impl Source for UeventMonitor { /// receive device from socket and insert into job queue fn dispatch(&self, _: &Events) -> i32 { let device = match self.device_monitor.receive_device() { - Ok(ret) => ret, + Ok(ret) => match ret { + Some(device) => device, + None => return 0, + }, Err(e) => { log::error!("Monitor Error: {}", e); return 0; diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml index fd1979ea..aced5a4f 100644 --- a/libs/basic/Cargo.toml +++ b/libs/basic/Cargo.toml @@ -20,6 +20,7 @@ pathdiff = { version = "0.2.1", optional = true } procfs = { version = "0.12.0", default-features = false, optional = true } rand = { version = "0.8.5", optional = true } snafu = { version = "0.7", features = ["std"], default-features = false } +fnmatch-sys = "1.0.0" [dev-dependencies] tempfile = "3.6.0" diff --git a/libs/basic/src/error.rs b/libs/basic/src/error.rs index 89ac5a3e..2b4cffd8 100644 --- a/libs/basic/src/error.rs +++ b/libs/basic/src/error.rs @@ -144,6 +144,11 @@ pub fn errno_is_privilege(source: Errno) -> bool { matches!(source, Errno::EACCES | Errno::EPERM) } +/// two errno to try again +pub fn errno_is_transient(source: Errno) -> bool { + matches!(source, Errno::EAGAIN | Errno::EINTR) +} + #[cfg(test)] mod tests { use super::*; @@ -164,4 +169,11 @@ mod tests { assert!(errno_is_privilege(nix::Error::EACCES)); assert!(errno_is_privilege(nix::Error::EPERM)); } + + #[test] + fn test_errno_is_transient() { + assert!(errno_is_transient(nix::Error::EAGAIN)); + assert!(errno_is_transient(nix::Error::EINTR)); + assert!(!errno_is_transient(nix::Error::EACCES)); + } } diff --git a/libs/basic/src/socket_util.rs b/libs/basic/src/socket_util.rs index a69259b3..6de8e52c 100644 --- a/libs/basic/src/socket_util.rs +++ b/libs/basic/src/socket_util.rs @@ -12,6 +12,8 @@ //! use crate::error::*; +use crate::IN_SET; +use nix::sys::socket::{recv, MsgFlags}; use nix::{ errno::Errno, sys::socket::{self, sockopt, AddressFamily}, @@ -133,3 +135,70 @@ pub fn set_keepalive_probes(fd: RawFd, v: u32) -> Result<()> { pub fn set_broadcast_state(fd: RawFd, v: bool) -> Result<()> { socket::setsockopt(fd, sockopt::Broadcast, &v).context(NixSnafu) } + +/// get the size of data in fd +pub fn next_datagram_size_fd(fd: RawFd) -> Result { + /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will + * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't + * do. This difference is actually of major importance as we need to be sure that the size returned here + * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of + * the wrong size. + */ + + let mut buf = Vec::new(); + match recv(fd, &mut buf, MsgFlags::MSG_PEEK | MsgFlags::MSG_TRUNC) { + Ok(len) => { + if len != 0 { + return Ok(len); + } + } + Err(err) => { + if !IN_SET!(err, Errno::EOPNOTSUPP, Errno::EFAULT) { + return Err(Error::Nix { source: err }); + } + } + } + + /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD + * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. + */ + + let k: usize = 0; + Errno::clear(); + if unsafe { libc::ioctl(fd, libc::FIONREAD, &k) } < 0 { + return Err(Error::Nix { + source: Errno::from_i32(nix::errno::errno()), + }); + } + + Ok(k) +} + +#[cfg(test)] +mod tests { + use super::*; + use nix::sys::socket::{send, socketpair, AddressFamily, MsgFlags, SockFlag, SockType}; + use std::fs::{remove_file, File}; + use std::io::Write; + use std::os::unix::io::AsRawFd; + + #[test] + fn test_next_datagram_size_fd() { + let buf: Vec = vec![0, 1, 2, 3, 4]; + + let mut not_socket_file = File::create("/tmp/test_next_datagram_size_fd").unwrap(); + not_socket_file.write_all(&buf).unwrap(); + assert!(next_datagram_size_fd(not_socket_file.as_raw_fd()).is_err()); + remove_file("/tmp/test_next_datagram_size_fd").unwrap(); + + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::SOCK_CLOEXEC | SockFlag::SOCK_NONBLOCK, + ) + .unwrap(); + send(fd1, &buf, MsgFlags::empty()).unwrap(); + assert_eq!(next_datagram_size_fd(fd2).unwrap(), buf.len()); + } +} diff --git a/libs/basic/src/string.rs b/libs/basic/src/string.rs index cac31897..e2a7e378 100644 --- a/libs/basic/src/string.rs +++ b/libs/basic/src/string.rs @@ -12,6 +12,10 @@ //! Common used string functions +use fnmatch_sys::fnmatch; +use libc::{c_char, c_int}; +use std::ffi::CString; + /// Add "\n" to s. /// This can be used when generating a multi-line string. /// Use this function before you write a new line. @@ -20,3 +24,96 @@ pub fn new_line_break(s: &mut String) { *s += "\n"; } } + +/// Pattern match based on glob style pattern +/// The flags argument modifies the behavior; it is the bitwise OR of zero +/// or more of the following flags: +/// +/// `FNM_NOMATCH` +/// `FNM_NOESCAPE` +/// `FNM_PATHNAME` +/// `FNM_PERIOD` +/// +/// return: +/// true: if string matches. +/// false: 1.no match 2.pattern or value is error 3.fnmatch is error +pub fn pattern_match(pattern: &str, value: &str, flags: c_int) -> bool { + let cpattern = match CString::new(pattern) { + Ok(cpattern) => cpattern, + Err(_err) => return false, + }; + let cvalue = match CString::new(value) { + Ok(cvalue) => cvalue, + Err(_err) => return false, + }; + + unsafe { + fnmatch( + cpattern.as_ptr() as *const c_char, + cvalue.as_ptr() as *const c_char, + flags, + ) == 0 + } +} + +/// Pattern match +/// return: +/// true: 1.if string matches 2.pattern is empty +/// false: pattern is not empty and pattern_match return false +pub fn fnmatch_or_empty(pattern: &str, value: &str, flags: c_int) -> bool { + pattern.is_empty() || pattern_match(pattern, value, flags) +} + +#[cfg(test)] +mod tests { + use super::*; + use fnmatch_sys; + + #[test] + fn test_new_line_break() { + let mut s = String::from(""); + new_line_break(&mut s); + assert_eq!(s, ""); + + let mut s = String::from("abc"); + new_line_break(&mut s); + assert_eq!(s, "abc\n"); + } + + #[test] + fn test_pattern_match() { + assert!(pattern_match("hello*", "hello world", 0)); + assert!(!pattern_match("hello*", "world", 0)); + assert!(pattern_match("hello*", "hello world", unsafe { + fnmatch_sys::FNM_NOMATCH + })); + assert!(!pattern_match("hello*", "world", unsafe { + fnmatch_sys::FNM_NOMATCH + })); + + assert!(pattern_match("hello\\*", "hello\\ world", unsafe { + fnmatch_sys::FNM_NOESCAPE + })); + + assert!(pattern_match("foo/*", "foo/bar.txt", unsafe { + fnmatch_sys::FNM_PATHNAME + })); + assert!(!pattern_match("foo/*", "foo/subdir/bar.txt", unsafe { + fnmatch_sys::FNM_PATHNAME + })); + + assert!(pattern_match("*.txt", "bar.txt", unsafe { + fnmatch_sys::FNM_PERIOD + })); + assert!(!pattern_match("*.txt", ".txt", unsafe { + fnmatch_sys::FNM_PERIOD + })); + } + + #[test] + fn test_fnmatch_or_empty() { + assert!(fnmatch_or_empty("", "hello world", 0)); + assert!(fnmatch_or_empty("hello*", "hello world", 0)); + assert!(!fnmatch_or_empty("hello*", "world", 0)); + } +} diff --git a/libs/device/Cargo.toml b/libs/device/Cargo.toml index ecb9955c..83d3fbf5 100644 --- a/libs/device/Cargo.toml +++ b/libs/device/Cargo.toml @@ -13,6 +13,8 @@ basic = { path = "../basic", default-features = false, features = [ "fd", "murmurhash2", "uuid", + "string", + "socket", ] } event = { path = "../event" } log = { path = "../log" } diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs index 70070c72..ae36bc96 100644 --- a/libs/device/src/device.rs +++ b/libs/device/src/device.rs @@ -16,6 +16,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::string::fnmatch_or_empty; 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, @@ -2720,6 +2721,71 @@ impl Device { Ok(()) } + + /// check whether a device matches parent + pub fn match_parent( + &self, + match_parent: &HashSet, + nomatch_parent: &HashSet, + ) -> bool { + let syspath = match self.get_syspath() { + Ok(syspath) => syspath, + Err(_err) => return false, + }; + + for syspath_parent in nomatch_parent { + if syspath.starts_with(syspath_parent) { + return false; + } + } + + if match_parent.is_empty() { + return true; + } + + for syspath_parent in match_parent { + if syspath.starts_with(syspath_parent) { + return true; + } + } + + false + } + + /// check whether the sysattrs of a device matches + pub fn match_sysattr( + &self, + match_sysattr: &HashMap, + nomatch_sysattr: &HashMap, + ) -> bool { + for (sysattr, patterns) in match_sysattr { + if !self.match_sysattr_value(sysattr, patterns) { + return false; + } + } + + for (sysattr, patterns) in nomatch_sysattr { + if self.match_sysattr_value(sysattr, patterns) { + return false; + } + } + + true + } + + /// check whether the value of specific sysattr of a device matches + fn match_sysattr_value(&self, sysattr: &str, patterns: &str) -> bool { + let value = match self.get_sysattr_value(sysattr) { + Ok(value) => value, + Err(_) => return false, + }; + + if patterns.is_empty() { + return true; + } + + fnmatch_or_empty(patterns, &value, 0) + } } /// iterator wrapper of hash set in refcell @@ -3458,6 +3524,7 @@ mod tests { } if let Err(e) = LoopDev::inner_process("/tmp/test_update_tag", 1024 * 10, inner_test) { + println!("e:{:?}", e); assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); } } @@ -3810,4 +3877,36 @@ V:100 fn test_from_devnum_err() { assert!(Device::from_devnum('x', 100).is_err()); } + + #[test] + fn test_match_sysattr() { + let mut ert = DeviceEnumerator::new(); + let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); + + ert.add_match_sysattr("ifindex", "1", true).unwrap(); + ert.add_match_sysattr("address", "aa:aa:aa:aa:aa:aa", false) + .unwrap(); + + assert!(dev.match_sysattr(&ert.match_sysattr.borrow(), &ert.not_match_sysattr.borrow())); + + assert!(dev.match_sysattr(&ert.match_sysattr.borrow(), &HashMap::new())); + + let mut nomatch_sysattr = HashMap::new(); + nomatch_sysattr.insert("ifindex".to_string(), "1".to_string()); + assert!(!dev.match_sysattr(&HashMap::new(), &nomatch_sysattr)); + + assert!(dev.match_sysattr(&HashMap::new(), &HashMap::new())); + } + + #[test] + fn test_match_parent() { + let mut ert = DeviceEnumerator::new(); + let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); + ert.add_match_parent(&dev).unwrap(); + assert!(dev.match_parent(&ert.match_parent.borrow(), &HashSet::new())); + + let mut nomatch_parent = HashSet::new(); + nomatch_parent.insert("/sys/devices/virtual/net/lo".to_string()); + assert!(!dev.match_parent(&HashSet::new(), &nomatch_parent)); + } } diff --git a/libs/device/src/device_enumerator.rs b/libs/device/src/device_enumerator.rs index 29391e0b..78750156 100644 --- a/libs/device/src/device_enumerator.rs +++ b/libs/device/src/device_enumerator.rs @@ -14,14 +14,14 @@ //! use crate::{device::Device, error::*, utils::*, TAGS_BASE_DIR}; use bitflags::bitflags; -use fnmatch_sys::fnmatch; +//use fnmatch_sys::fnmatch; +use basic::string::pattern_match; use nix::errno::Errno; use snafu::ResultExt; use std::{ cell::RefCell, collections::{HashMap, HashSet}, iter::Iterator, - os::raw::c_char, path::Path, rc::Rc, }; @@ -455,11 +455,11 @@ impl DeviceEnumerator { for (property_pattern, value_pattern) in self.match_property.borrow().iter() { for (property, value) in &device.property_iter() { - if !self.pattern_match(property_pattern, property) { + if !pattern_match(property_pattern, property, 0) { continue; } - if self.pattern_match(value_pattern, value) { + if pattern_match(value_pattern, value, 0) { return Ok(true); } } @@ -498,57 +498,6 @@ impl DeviceEnumerator { ) } - /// check whether a device matches parent - pub(crate) fn match_parent(&self, device: &Device) -> Result { - if self.match_parent.borrow().is_empty() { - return Ok(true); - } - - for parent in self.match_parent.borrow().iter() { - if device.syspath.borrow().starts_with(parent) { - return Ok(true); - } - } - - Ok(false) - } - - /// check whether the sysattrs of a device matches - pub(crate) fn match_sysattr(&self, device: &Device) -> Result { - for (sysattr, patterns) in self.match_sysattr.borrow().iter() { - if !self.match_sysattr_value(device, sysattr, patterns)? { - return Ok(false); - } - } - - for (sysattr, patterns) in self.not_match_sysattr.borrow().iter() { - if self.match_sysattr_value(device, sysattr, patterns)? { - return Ok(false); - } - } - - Ok(true) - } - - /// check whether the value of specific sysattr of a device matches - pub(crate) fn match_sysattr_value( - &self, - device: &Device, - sysattr: &str, - patterns: &str, - ) -> Result { - let value = match device.get_sysattr_value(sysattr) { - Ok(value) => value, - Err(_) => return Ok(false), - }; - - if patterns.is_empty() { - return Ok(true); - } - - Ok(self.pattern_match(patterns, &value)) - } - /// check whether a device matches conditions according to flags pub(crate) fn test_matches( &self, @@ -579,7 +528,9 @@ impl DeviceEnumerator { } } - if (flags & MatchFlag::PARENT).bits() != 0 && !self.match_parent(device)? { + if (flags & MatchFlag::PARENT).bits() != 0 + && !device.match_parent(&self.match_parent.borrow(), &HashSet::new()) + { return Ok(false); } @@ -595,7 +546,10 @@ impl DeviceEnumerator { return Ok(false); } - if !self.match_sysattr(device)? { + if !device.match_sysattr( + &self.match_sysattr.borrow(), + &self.not_match_sysattr.borrow(), + ) { return Ok(false); } @@ -1139,20 +1093,6 @@ impl DeviceEnumerator { ret } - /// Pattern match based on glob style pattern - pub(crate) fn pattern_match(&self, pattern: &str, value: &str) -> bool { - let pattern = format!("{}\0", pattern); - let value = format!("{}\0", value); - - unsafe { - fnmatch( - pattern.as_ptr() as *const c_char, - value.as_ptr() as *const c_char, - 0, - ) == 0 - } - } - /// if any exclude pattern matches, return false /// if include pattern set is empty, return true /// if any include pattern matches, return true, else return false @@ -1163,7 +1103,7 @@ impl DeviceEnumerator { value: &str, ) -> bool { for pattern in exclude_pattern_set.iter() { - if self.pattern_match(pattern, value) { + if pattern_match(pattern, value, 0) { return false; } } @@ -1173,7 +1113,7 @@ impl DeviceEnumerator { } for pattern in include_pattern_set.iter() { - if self.pattern_match(pattern, value) { + if pattern_match(pattern, value, 0) { return true; } } @@ -1184,9 +1124,9 @@ impl DeviceEnumerator { #[cfg(test)] mod tests { - use crate::{device_enumerator::DeviceEnumerationType, Device}; - use super::{DeviceEnumerator, MatchInitializedType}; + use crate::{device_enumerator::DeviceEnumerationType, Device}; + use std::collections::HashSet; #[test] fn test_enumerator_inialize() { @@ -1242,13 +1182,6 @@ mod tests { } } - #[test] - fn test_pattern_match() { - let enumerator = DeviceEnumerator::new(); - assert!(enumerator.pattern_match("hello*", "hello world")); - assert!(!enumerator.pattern_match("hello*", "world")); - } - trait State { fn trans(self: Box, s: &str) -> Box; } @@ -1342,18 +1275,6 @@ mod tests { assert!(!ert.match_subsystem("block")); } - #[test] - fn test_match_sysattr() { - let mut ert = DeviceEnumerator::new(); - let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); - - ert.add_match_sysattr("ifindex", "1", true).unwrap(); - ert.add_match_sysattr("address", "aa:aa:aa:aa:aa:aa", false) - .unwrap(); - - assert!(ert.match_sysattr(&dev).unwrap()); - } - #[test] fn test_match_sysname() { let mut ert = DeviceEnumerator::new(); @@ -1383,24 +1304,16 @@ mod tests { fn test_match_parent_incremental() { let mut ert = DeviceEnumerator::new(); let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); - assert!(ert.match_parent(&dev).unwrap()); + assert!(dev.match_parent(&ert.match_parent.borrow(), &HashSet::new())); ert.add_match_parent_incremental(&dev).unwrap(); - assert!(ert.match_parent(&dev).unwrap()); + assert!(dev.match_parent(&ert.match_parent.borrow(), &HashSet::new())); let dev_1 = Device::from_subsystem_sysname("drivers", "usb:usb").unwrap(); - assert!(!ert.match_parent(&dev_1).unwrap()); + assert!(!dev_1.match_parent(&ert.match_parent.borrow(), &HashSet::new())); ert.set_enumerator_type(DeviceEnumerationType::Devices); ert.scan_devices().unwrap(); } - #[test] - fn test_match_parent() { - let mut ert = DeviceEnumerator::new(); - let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); - ert.add_match_parent_incremental(&dev).unwrap(); - assert!(ert.match_parent(&dev).unwrap()); - } - #[test] fn test_match_is_initialized() { let mut ert = DeviceEnumerator::new(); diff --git a/libs/device/src/device_monitor.rs b/libs/device/src/device_monitor.rs index c6ad3ec6..c7a488b6 100644 --- a/libs/device/src/device_monitor.rs +++ b/libs/device/src/device_monitor.rs @@ -12,17 +12,19 @@ //! device monitor //! +use crate::{device::Device, error::Error}; +use basic::errno_is_transient; use basic::murmurhash2::murmurhash2; +use basic::socket_util::next_datagram_size_fd; use nix::{ errno::Errno, sys::socket::{ recv, sendmsg, AddressFamily, MsgFlags, NetlinkAddr, SockFlag, SockProtocol, SockType, }, }; +use std::collections::{HashMap, HashSet}; use std::{io::IoSlice, mem::size_of, os::unix::prelude::RawFd}; -use crate::{device::Device, error::Error}; - const UDEV_MONITOR_MAGIC: u32 = 0xfeedcafe; /// Compatible with 'string_hash32' in libsystemd @@ -100,6 +102,15 @@ pub struct DeviceMonitor { socket: RawFd, /// socket address, currently only support netlink _sockaddr: NetlinkAddr, + + /// key:subsystem value:devtype + subsystem_filter: HashMap, + tag_filter: HashSet, + match_sysattr_filter: HashMap, + nomatch_sysattr_filter: HashMap, + match_parent_filter: HashSet, + nomatch_parent_filter: HashSet, + filter_uptodate: bool, } impl DeviceMonitor { @@ -122,6 +133,13 @@ impl DeviceMonitor { DeviceMonitor { socket: sock, _sockaddr: sa, + subsystem_filter: HashMap::new(), + tag_filter: HashSet::new(), + match_sysattr_filter: HashMap::new(), + nomatch_sysattr_filter: HashMap::new(), + match_parent_filter: HashSet::new(), + nomatch_parent_filter: HashSet::new(), + filter_uptodate: false, } } @@ -131,8 +149,22 @@ impl DeviceMonitor { } /// receive device - pub fn receive_device(&self) -> Result { - let mut buf = vec![0; 1024 * 8]; + pub fn receive_device(&self) -> Result, Error> { + let n = match next_datagram_size_fd(self.socket) { + Ok(n) => n, + Err(err) => { + let e = Errno::from_i32(err.get_errno()); + if !errno_is_transient(e) { + log::error!("Failed to get the received message size err:{:?}", err); + } + return Err(Error::Nix { + msg: "".to_string(), + source: e, + }); + } + }; + + let mut buf = vec![0; n]; let n = match recv(self.socket, &mut buf, MsgFlags::empty()) { Ok(ret) => ret, Err(errno) => { @@ -153,16 +185,42 @@ impl DeviceMonitor { let prefix = String::from_utf8(buf[..prefix_split_idx].to_vec()).unwrap(); + let device: Device; if prefix.contains("@/") { - return Device::from_nulstr(&buf[prefix_split_idx + 1..n]); + device = match Device::from_nulstr(&buf[prefix_split_idx + 1..n]) { + Ok(device) => device, + Err(err) => return Err(err), + }; } else if prefix == "libudev" { - return Device::from_nulstr(&buf[40..n]); + device = match Device::from_nulstr(&buf[40..n]) { + Ok(device) => device, + Err(err) => return Err(err), + }; + } else { + return Err(Error::Nix { + msg: format!("invalid nulstr data ({:?})", buf), + source: Errno::EINVAL, + }); } - Err(Error::Nix { - msg: format!("invalid nulstr data ({:?})", buf), - source: Errno::EINVAL, - }) + /* Skip device, if it does not pass the current filter */ + match self.passes_filter(&device) { + Ok(flag) => { + if !flag { + log::trace!("Received device does not pass filter, ignoring."); + Ok(None) + } else { + Ok(Some(device)) + } + } + Err(err) => { + log::error!( + "Failed to check received device passing filter err:{:?}", + err + ); + Err(err) + } + } } /// send device @@ -202,6 +260,107 @@ impl DeviceMonitor { Ok(()) } + + /// add subsystem and devtype match + pub fn filter_add_match_subsystem_devtype( + &mut self, + subsystem: &str, + devtype: &str, + ) -> Result<(), Error> { + if subsystem.is_empty() { + return Err(Error::Nix { + msg: "subsystem is empty".to_string(), + source: Errno::EINVAL, + }); + } + + self.subsystem_filter + .insert(subsystem.to_string(), devtype.to_string()); + self.filter_uptodate = false; + + Ok(()) + } + + /// add tag match + pub fn filter_add_match_tag(&mut self, tag: &str) -> Result<(), Error> { + if tag.is_empty() { + return Err(Error::Nix { + msg: "tag is empty".to_string(), + source: Errno::EINVAL, + }); + } + + self.tag_filter.insert(tag.to_string()); + self.filter_uptodate = false; + + Ok(()) + } + + fn passes_filter(&self, device: &Device) -> Result { + match self.check_subsystem_filter(device) { + Ok(flag) => { + if !flag { + return Ok(false); + } + } + Err(err) => return Err(err), + } + + if !self.check_tag_filter(device) { + return Ok(false); + } + + if !device.match_sysattr(&self.match_sysattr_filter, &self.nomatch_sysattr_filter) { + return Ok(false); + } + + Ok(device.match_parent(&self.match_parent_filter, &self.nomatch_parent_filter)) + } + + fn check_subsystem_filter(&self, device: &Device) -> Result { + if self.subsystem_filter.is_empty() { + return Ok(true); + } + + let subsystem = match device.get_subsystem() { + Ok(subsystem) => subsystem, + Err(err) => return Err(err), + }; + + let devtype = match device.get_devtype() { + Ok(devtype) => devtype, + Err(err) => { + if err.get_errno() != nix::Error::ENOENT { + return Err(err); + } else { + String::from("") + } + } + }; + + for (key, value) in &self.subsystem_filter { + if key != &subsystem { + continue; + } + if value.is_empty() || value == &devtype { + return Ok(true); + } + } + + Ok(false) + } + + fn check_tag_filter(&self, device: &Device) -> bool { + if self.tag_filter.is_empty() { + return true; + } + for tag in &self.tag_filter { + if let Ok(true) = device.has_tag(tag) { + return true; + } + } + false + } } impl Drop for DeviceMonitor { @@ -249,7 +408,7 @@ mod tests { /// fn dispatch(&self, e: &Events) -> i32 { - if let Ok(device) = self.device_monitor.receive_device() { + if let Ok(Some(device)) = self.device_monitor.receive_device() { println!("{}", device.get_device_id().unwrap()); } e.set_exit(); @@ -320,4 +479,25 @@ mod tests { e.del_source(s.clone()).unwrap(); } + + #[test] + fn test_filter_add_match_subsystem_devtype() { + let mut device_monitor = DeviceMonitor::new(MonitorNetlinkGroup::Userspace, None); + assert!(device_monitor + .filter_add_match_subsystem_devtype("", "") + .is_err()); + device_monitor + .filter_add_match_subsystem_devtype("net", "") + .unwrap(); + device_monitor + .filter_add_match_subsystem_devtype("block", "disk") + .unwrap(); + } + + #[test] + fn test_filter_add_match_tag() { + let mut device_monitor = DeviceMonitor::new(MonitorNetlinkGroup::Userspace, None); + assert!(device_monitor.filter_add_match_tag("").is_err()); + device_monitor.filter_add_match_tag("sysmaster").unwrap(); + } } -- Gitee