sysmaster/backport-feature-devmaster-Add-subcommands-for-devctl-trigger.patch

1344 lines
44 KiB
Diff
Raw Normal View History

2023-12-07 00:19:38 +08:00
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