From ac13301c681cd4a4b50e4874b8f6c1f7b81e9f13 Mon Sep 17 00:00:00 2001
From: huyubiao <huyubiao@huawei.com>
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<T> = std::result::Result<T, nix::Error>;
+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<String>,
 
-        /// 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<String>,
 
-        /// 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<Vec<String>>,
+
+        /// Exclude devices from a matching subsystem
+        #[clap(short('S'), long)]
+        subsystem_nomatch: Option<Vec<String>>,
+
+        /// Trigger devices with a matching attribute
+        #[clap(short('a'), long)]
+        attr_match: Option<Vec<String>>,
+
+        /// Exclude devices with a matching attribute
+        #[clap(short('A'), long)]
+        attr_nomatch: Option<Vec<String>>,
+
+        /// Trigger devices with a matching property
+        #[clap(short('p'), long)]
+        property_match: Option<Vec<String>>,
+
+        /// Trigger devices with a matching tag
+        #[clap(short('g'), long)]
+        tag_match: Option<Vec<String>>,
+
+        /// Trigger devices with this /sys path
+        #[clap(short('y'), long)]
+        sysname_match: Option<Vec<String>>,
+
+        /// Trigger devices with this /dev name
+        #[clap(long)]
+        name_match: Option<Vec<String>>,
+
+        /// Trigger devices with this /sys path
+        #[clap(short('b'), long)]
+        parent_match: Option<Vec<String>>,
+
+        /// 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<String>,
-
-        /// 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<T> = std::result::Result<T, nix::Error>;
-
 #[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<String>,
+#[derive(Debug)]
+pub struct TriggerArgs {
+    action: Option<String>,
     r#type: Option<String>,
     verbose: bool,
-    action: Option<String>,
     dry_run: bool,
-) {
-    let mut devlist = vec![];
+    subsystem_match: Option<Vec<String>>,
+    subsystem_nomatch: Option<Vec<String>>,
+    attr_match: Option<Vec<String>>,
+    attr_nomatch: Option<Vec<String>>,
+    property_match: Option<Vec<String>>,
+    tag_match: Option<Vec<String>>,
+    sysname_match: Option<Vec<String>>,
+    name_match: Option<Vec<String>>,
+    parent_match: Option<Vec<String>>,
+    settle: bool,
+    uuid: bool,
+    devices: Vec<String>,
+}
+
+impl TriggerArgs {
+    #[allow(clippy::too_many_arguments)]
+    pub fn new(
+        action: Option<String>,
+        r#type: Option<String>,
+        verbose: bool,
+        dry_run: bool,
+        subsystem_match: Option<Vec<String>>,
+        subsystem_nomatch: Option<Vec<String>>,
+        attr_match: Option<Vec<String>>,
+        attr_nomatch: Option<Vec<String>>,
+        property_match: Option<Vec<String>>,
+        tag_match: Option<Vec<String>>,
+        sysname_match: Option<Vec<String>>,
+        name_match: Option<Vec<String>>,
+        parent_match: Option<Vec<String>>,
+        settle: bool,
+        uuid: bool,
+        devices: Vec<String>,
+    ) -> 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::<DeviceAction>().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::<DeviceAction>().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<HashSet<String>> {
+        let mut uuid_supported = -1;
+        let mut uuids = HashSet::new();
+        let mut ret = Ok(HashSet::<String>::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<HashSet<String>>,
+    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<String>,
+        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<T> = std::result::Result<T, nix::Error>;
 
@@ -11,14 +12,22 @@ pub fn find_device(id: &str, prefix: &str) -> Result<Device> {
         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<T> = std::result::Result<T, nix::Error>;
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<usize, ()> {
-    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<TimeSpec>) -> Result<libc::c_int> {
@@ -58,3 +60,53 @@ pub fn wait_for_events(fd: RawFd, event: PollFlags, time_out: i64) -> Result<lib
 
     Ok(ret)
 }
+
+/// Read data from file to buf, and return the number of bytes read.
+pub fn loop_read(file: &mut File, buf: &mut [u8]) -> Result<usize> {
+    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<u8, ()> {
@@ -167,6 +167,33 @@ impl Uuid {
     }
 }
 
+/// get random uuid
+pub fn randomize() -> Result<Uuid, nix::Error> {
+    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<Option<Uuid>, 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<Option<Uuid>, 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<HashMap<String, HashSet<String>>>,
+    pub(crate) match_sysattr: RefCell<HashMap<String, String>>,
     /// do not match sysattr
     /// key: sysattr, value: match value
-    pub(crate) not_match_sysattr: RefCell<HashMap<String, HashSet<String>>>,
+    pub(crate) not_match_sysattr: RefCell<HashMap<String, String>>,
 
     /// 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<String>,
+        device: &Device,
+        sysattr: &str,
+        patterns: &str,
     ) -> Result<bool, Error> {
-        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<bool, Error> {
-        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

