diff --git a/exts/devmaster/src/bin/devctl/main.rs b/exts/devmaster/src/bin/devctl/main.rs index 68b053ac059bbffafeb7cd003dfa75e2964f6274..41b905e552934a45a9f673218f3d7e50750daecf 100644 --- a/exts/devmaster/src/bin/devctl/main.rs +++ b/exts/devmaster/src/bin/devctl/main.rs @@ -20,10 +20,13 @@ use log::init_log_to_console_syslog; 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_test_builtin::subcommand_test_builtin; use subcmds::devctl_trigger::subcommand_trigger; +type Result = std::result::Result; + /// parse program arguments #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -36,16 +39,63 @@ struct Args { /// Kinds of subcommands #[derive(Parser, Debug)] enum SubCmd { - /// Monitor device events from kernel and userspace + /// Query sysfs or the udev database #[clap(display_order = 1)] + Info { + #[clap(short, long, possible_values(&["name", "symlink", "path", "property", "all"]), help( + "Query device information:\n\ + name Name of device node\n\ + symlink Pointing to node\n\ + path sysfs device path\n\ + property or env The device properties\n\ + all All values\n") + )] + query: Option, + + /// Print all key matches walking along the chain + /// of parent devices + #[clap(short, long)] + attribute_walk: bool, + + /// Print major:minor of device containing this file + #[clap(short, long)] + device_id_of_file: Option, + + /// Export key/value pairs + #[clap(short('x'), long)] + export: bool, + + /// Export the key name with a prefix + #[clap(short('P'), long)] + export_prefix: Option, + + /// Export the content of the devmaster database + #[clap(short('e'), long)] + export_db: bool, + + /// Clean up the devmaster database + #[clap(short, long)] + cleanup_db: bool, + + /// Prepend dev directory to path names + #[clap(short, long)] + root: bool, + + /// + #[clap(required = false)] + devices: Vec, + }, + + /// Monitor device events from kernel and userspace + #[clap(display_order = 2)] Monitor {}, /// Kill all devmaster workers - #[clap(display_order = 2)] + #[clap(display_order = 3)] Kill {}, /// Trigger a fake device action, then the kernel will report an uevent - #[clap(display_order = 3)] + #[clap(display_order = 4)] Trigger { /// the kind of device action to trigger #[clap(short, long)] @@ -69,7 +119,7 @@ enum SubCmd { }, /// Test builtin command on a device - #[clap(display_order = 4)] + #[clap(display_order = 5)] TestBuiltin { /// device action #[clap(short, long)] @@ -82,7 +132,7 @@ enum SubCmd { syspath: String, }, /// Test builtin command on a device - #[clap(display_order = 5)] + #[clap(display_order = 6)] Hwdb { /// update the hardware database #[clap(short('u'), long)] @@ -111,11 +161,35 @@ fn subcommand_kill() { stream.write_all(b"kill ").unwrap(); } -fn main() { +fn main() -> Result<()> { init_log_to_console_syslog("devctl", Level::Debug); let args = Args::parse(); match args.subcmd { + SubCmd::Info { + query, + attribute_walk, + device_id_of_file, + export, + export_prefix, + export_db, + cleanup_db, + root, + devices, + } => { + return InfoArgs::new( + query, + attribute_walk, + device_id_of_file, + export, + export_prefix, + export_db, + cleanup_db, + root, + devices, + ) + .subcommand_info() + } SubCmd::Monitor {} => subcommand_monitor(), SubCmd::Kill {} => subcommand_kill(), SubCmd::Trigger { @@ -139,4 +213,6 @@ fn main() { root, } => subcommand_hwdb(update, test, path, usr, strict, root), } + + Ok(()) } diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..824e69dc5d6172f26efd2a2b55a2af1a69cf57b8 --- /dev/null +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs @@ -0,0 +1,669 @@ +// 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. + +//! subcommand for devctl trigger + +use crate::subcmds::devctl_utils; +use basic::fd_util::{dot_or_dot_dot, xopendirat}; +use device::{device_enumerator::DeviceEnumerator, Device}; +use nix::dir::Dir; +use nix::fcntl::{AtFlags, OFlag}; +use nix::sys::stat::fstatat; +use nix::sys::stat::Mode; +use nix::unistd::{unlinkat, UnlinkatFlags}; +use std::fs; +use std::os::unix::fs::MetadataExt; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +type Result = std::result::Result; + +#[derive(Debug)] +enum QueryType { + Name, + Path, + Symlink, + Property, + All, +} + +struct QueryProperty { + export: bool, + export_prefix: Option, +} + +impl QueryProperty { + fn new(export: bool, export_prefix: Option) -> Self { + QueryProperty { + export, + export_prefix, + } + } +} + +struct SysAttr { + name: String, + value: String, +} + +pub struct InfoArgs { + query: Option, + attribute_walk: bool, + device_id_of_file: Option, + export: bool, + export_prefix: Option, + export_db: bool, + cleanup_db: bool, + root: bool, + devices: Vec, +} + +impl InfoArgs { + #[allow(clippy::too_many_arguments)] + pub fn new( + query: Option, + attribute_walk: bool, + device_id_of_file: Option, + export: bool, + export_prefix: Option, + export_db: bool, + cleanup_db: bool, + root: bool, + devices: Vec, + ) -> Self { + InfoArgs { + query, + attribute_walk, + device_id_of_file, + export, + export_prefix, + export_db, + cleanup_db, + root, + devices, + } + } + + /// subcommand for hwdb a fake device action, then the kernel will report an uevent + pub fn subcommand_info(&self) -> Result<()> { + let mut devs = Vec::new(); + + let mut arg_export = false; + if self.export || self.export_prefix.is_some() { + arg_export = true; + } + + if self.export_db { + return export_devices(); + } + if self.cleanup_db { + return cleanup_db(); + } + + if let Some(name) = self.device_id_of_file.as_ref() { + if !self.devices.is_empty() { + log::error!("Positional arguments are not allowed with -d/--device-id-of-file."); + return Err(nix::Error::EINVAL); + } + return self.stat_device(name); + } + + let mut query_type = QueryType::All; + self.parse_query_type(&mut query_type)?; + + devs.extend(&self.devices); + if devs.is_empty() { + log::error!("A device name or path is required"); + return Err(nix::Error::EINVAL); + } + + if self.attribute_walk && devs.len() > 1 { + log::error!("Only one device may be specified with -a/--attribute-walk"); + return Err(nix::Error::EINVAL); + } + + let mut r: Result<()> = Ok(()); + for dev in &self.devices { + let device = match devctl_utils::find_device(dev, "") { + Ok(d) => d, + Err(e) => { + if e == nix::Error::EINVAL { + log::error!("Bad argument {:?}, expected an absolute path in /dev/ or /sys/ or a unit name", dev); + } else { + log::error!("Unknown device {:?}", dev); + } + continue; + } + }; + + if self.query.is_some() { + let query_property = QueryProperty::new(arg_export, self.export_prefix.clone()); + r = self.query_device(&query_type, device, query_property); + } else if self.attribute_walk { + r = print_device_chain(device); + } else { + log::error!("unknown action"); + return Err(nix::Error::EINVAL); + } + } + + r + } + + fn parse_query_type(&self, query_type: &mut QueryType) -> Result<()> { + match &self.query { + Some(q) => { + if q == "property" || q == "env" { + *query_type = QueryType::Property; + } else if q == "name" { + *query_type = QueryType::Name; + } else if q == "symlink" { + *query_type = QueryType::Symlink; + } else if q == "path" { + *query_type = QueryType::Path; + } else if q == "all" { + *query_type = QueryType::All; + } else { + log::error!("unknown query type"); + return Err(nix::Error::EINVAL); + } + } + None => *query_type = QueryType::All, + } + Ok(()) + } + + fn stat_device(&self, name: &str) -> Result<()> { + let metadata = match fs::metadata(name) { + Ok(metadata) => metadata, + Err(err) => { + log::error!("Failed to get metadata:{:?} err:{:?}", name, err); + return Err(nix::Error::EINVAL); + } + }; + + if self.export { + match &self.export_prefix { + Some(p) => { + println!("{}MAJOR={}", p, nix::sys::stat::major(metadata.dev())); + println!("{}MINOR={}", p, nix::sys::stat::minor(metadata.dev())); + } + None => { + println!("INFO_MAJOR={}", nix::sys::stat::major(metadata.dev())); + println!("INFO_MINOR={}", nix::sys::stat::minor(metadata.dev())); + } + } + } else { + println!( + "{}:{}", + nix::sys::stat::major(metadata.dev()), + nix::sys::stat::minor(metadata.dev()) + ); + } + + Ok(()) + } + + fn query_device( + &self, + query: &QueryType, + device: Device, + property: QueryProperty, + ) -> Result<()> { + match query { + QueryType::Name => { + let node = match device.get_devname() { + Ok(node) => node, + Err(err) => { + log::error!("No device node found"); + return Err(err.get_errno()); + } + }; + + if !self.root { + println!( + "{}", + Path::new(&node) + .strip_prefix("/dev/") + .unwrap() + .to_str() + .unwrap() + ); + } else { + println!("{}", node); + } + Ok(()) + } + QueryType::Symlink => { + let mut devlinks_str = String::new(); + for devlink in &device.devlink_iter() { + if !self.root { + devlinks_str += Path::new(&devlink) + .strip_prefix("/dev/") + .unwrap() + .to_str() + .unwrap(); + } else { + devlinks_str += devlink; + } + devlinks_str += " "; + } + devlinks_str = devlinks_str.trim_end().to_string(); + + println!("{}", devlinks_str); + Ok(()) + } + QueryType::Path => { + let devpath = match device.get_devpath() { + Ok(devpath) => devpath, + Err(err) => { + log::error!("Failed to get device path"); + return Err(err.get_errno()); + } + }; + + println!("{}", devpath); + Ok(()) + } + QueryType::Property => { + for (key, value) in &device.property_iter() { + if property.export { + match &property.export_prefix { + Some(export_prefix) => println!("{}{}='{}'", export_prefix, key, value), + None => println!("{}='{}'", key, value), + } + } else { + println!("{}={}", key, value); + } + } + Ok(()) + } + QueryType::All => { + print_record(device, ""); + Ok(()) + } + } + } +} + +fn print_device_chain(device: Device) -> Result<()> { + println!( + " + \n\ + Udevadm info starts with the device specified by the devpath and then\n\ + walks up the chain of parent devices. It prints for every device\n\ + found, all possible attributes in the udev rules key format.\n\ + A rule to match, can be composed by the attributes of the device\n\ + and the attributes from one single parent device.\n\ + " + ); + + print_all_attributes(&device, false)?; + + let mut child = device; + while let Ok(parent) = child.get_parent() { + print_all_attributes(&parent.borrow(), true)?; + + child = parent.borrow().clone(); + } + + Ok(()) +} + +fn print_all_attributes(device: &Device, is_parent: bool) -> Result<()> { + let mut devpath = String::from(""); + let mut sysname = String::from(""); + let mut subsystem = String::from(""); + let mut driver = String::from(""); + + if let Ok(value) = device.get_devpath() { + devpath = value; + } + + if let Ok(value) = device.get_sysname() { + sysname = value; + } + + if let Ok(value) = device.get_subsystem() { + subsystem = value; + } + + if let Ok(value) = device.get_driver() { + driver = value; + } + + if is_parent { + println!(" looking at parent device '{}':", devpath); + println!(" KERNELS=={:?}", sysname); + println!(" SUBSYSTEMS=={:?}", subsystem); + println!(" DRIVERS=={:?}", driver); + } else { + println!(" looking at device '{}':", devpath); + println!(" KERNEL=={:?}", sysname); + println!(" SUBSYSTEM=={:?}", subsystem); + println!(" DRIVER=={:?}", driver); + } + + let mut sysattrs: Vec = Vec::new(); + + let iter = device.sysattr_iter(); + for name in &iter { + if skip_attribute(name) { + continue; + } + + let value = match device.get_sysattr_value(name) { + Ok(value) => { + /* skip any values that look like a path */ + if value.starts_with('/') { + continue; + } + if !value + .chars() + .all(|c| 0 != unsafe { libc::isprint(c as i32) }) + { + continue; + } + + value + } + Err(e) => { + if e.get_errno() == nix::Error::EACCES || e.get_errno() == nix::Error::EPERM { + "(not readable)".to_string() + } else { + continue; + } + } + }; + + sysattrs.push(SysAttr { + name: name.to_string(), + value, + }); + } + + sysattrs.sort_by(|a, b| a.name.cmp(&b.name)); + + for sysattr in sysattrs { + if is_parent { + println!(" ATTRS{{{}}}=={:?}", sysattr.name, sysattr.value); + } else { + println!(" ATTR{{{}}}=={:?}", sysattr.name, sysattr.value); + } + } + println!(); + + Ok(()) +} + +fn skip_attribute(name: &str) -> bool { + /* Those are either displayed separately or should not be shown at all. */ + if name.contains("uevent") + || name.contains("dev") + || name.contains("modalias") + || name.contains("resource") + || name.contains("driver") + || name.contains("subsystem") + || name.contains("module") + { + return true; + } + + false +} + +fn print_record(device: Device, prefix: &str) { + if let Ok(devpath) = device.get_devpath() { + println!("{}P: {}", prefix, devpath); + } + + if let Ok(sysname) = device.get_sysname() { + println!("{}M: {}", prefix, sysname); + } + + if let Ok(sysnum) = device.get_sysnum() { + println!("{}R: {}", prefix, sysnum); + } + + let mut subsys = String::from(""); + if let Ok(subsystem) = device.get_subsystem() { + subsys = subsystem.clone(); + println!("{}U: {}", prefix, subsystem); + } + + if let Ok(devnum) = device.get_devnum() { + if &subsys == "block" { + println!( + "{}D: b {}:{}", + prefix, + nix::sys::stat::major(devnum), + nix::sys::stat::minor(devnum) + ); + } else { + println!( + "{}D: c {}:{}", + prefix, + nix::sys::stat::major(devnum), + nix::sys::stat::minor(devnum) + ); + } + } + + if let Ok(ifindex) = device.get_ifindex() { + println!("{}I: {}", prefix, ifindex); + } + + if let Ok(devname) = device.get_devname() { + let val = Path::new(&devname) + .strip_prefix("/dev/") + .unwrap() + .to_str() + .unwrap(); + println!("{}N: {}", prefix, val); + if let Ok(i) = device.get_devlink_priority() { + println!("{}L: {}", prefix, i); + } + + for link in &device.devlink_iter() { + let val = Path::new(&link) + .strip_prefix("/dev/") + .unwrap() + .to_str() + .unwrap(); + println!("{}S: {}", prefix, val); + } + } + + if let Ok(q) = device.get_diskseq() { + println!("{}Q: {}", prefix, q); + } + + if let Ok(driver) = device.get_driver() { + println!("{}V: {}", prefix, driver); + } + + for (key, val) in &device.property_iter() { + println!("{}E: {}={}", prefix, key, val); + } + + if prefix.is_empty() { + println!(); + } +} + +fn export_devices() -> Result<()> { + let mut e = DeviceEnumerator::new(); + + if let Err(err) = e.allow_uninitialized() { + log::error!("Failed to set allowing uninitialized flag"); + return Err(err.get_errno()); + } + + if let Err(err) = e.scan_devices() { + log::error!("Failed to scan devices"); + return Err(err.get_errno()); + } + + for device in e.iter() { + print_record(device.borrow().clone(), ""); + } + + Ok(()) +} + +fn cleanup_db() -> Result<()> { + if let Ok(mut dir1) = Dir::open("/run/udev/data", OFlag::O_DIRECTORY, Mode::empty()) { + cleanup_dir(&mut dir1, libc::S_ISVTX, 1); + + if let Ok(mut dir2) = Dir::open("/run/udev/links", OFlag::O_DIRECTORY, Mode::empty()) { + cleanup_dirs_after_db_cleanup(&mut dir2, &dir1); + } + + if let Ok(mut dir3) = Dir::open("/run/udev/tags", OFlag::O_DIRECTORY, Mode::empty()) { + cleanup_dirs_after_db_cleanup(&mut dir3, &dir1); + } + } + + if let Ok(mut dir) = Dir::open( + "/run/udev/static_node-tags", + OFlag::O_DIRECTORY, + Mode::empty(), + ) { + cleanup_dir(&mut dir, 0, 2); + } + + Ok(()) +} + +fn cleanup_dir(dir: &mut Dir, mask: libc::mode_t, depth: i32) { + if depth <= 0 { + return; + } + + let dir_raw_fd = dir.as_raw_fd(); + for entry in dir.iter() { + let dent = match entry { + Ok(dent) => dent, + Err(_) => continue, + }; + + if dot_or_dot_dot(dent.file_name().to_str().unwrap()) { + continue; + } + + let stats = match fstatat(dir_raw_fd, dent.file_name(), AtFlags::AT_SYMLINK_NOFOLLOW) { + Ok(stats) => stats, + Err(_) => continue, + }; + + if (stats.st_mode & mask) != 0 { + continue; + } + + if stats.st_mode & libc::S_IFMT == libc::S_IFDIR { + match xopendirat( + dir_raw_fd, + dent.file_name().to_str().unwrap(), + OFlag::O_NOFOLLOW, + ) { + Ok(mut subdir) => cleanup_dir(&mut subdir, mask, depth - 1), + Err(e) => log::error!( + "Failed to open subdirectory {:?}, err{:?}, ignoring", + dent.file_name(), + e + ), + } + let _ = unlinkat(Some(dir_raw_fd), dent.file_name(), UnlinkatFlags::RemoveDir); + } else { + let _ = unlinkat( + Some(dir_raw_fd), + dent.file_name(), + UnlinkatFlags::NoRemoveDir, + ); + } + } +} + +fn cleanup_dirs_after_db_cleanup(dir: &mut Dir, datadir: &Dir) { + let dir_raw_fd = dir.as_raw_fd(); + for entry in dir.iter() { + let dent = match entry { + Ok(dent) => dent, + Err(_) => continue, + }; + + if dot_or_dot_dot(dent.file_name().to_str().unwrap()) { + continue; + } + + let stats = match fstatat(dir_raw_fd, dent.file_name(), AtFlags::AT_SYMLINK_NOFOLLOW) { + Ok(stats) => stats, + Err(_) => continue, + }; + + if stats.st_mode & libc::S_IFMT == libc::S_IFDIR { + match xopendirat( + dir_raw_fd, + dent.file_name().to_str().unwrap(), + OFlag::O_NOFOLLOW, + ) { + Ok(mut subdir) => cleanup_dir_after_db_cleanup(&mut subdir, datadir), + Err(e) => log::error!( + "Failed to open subdirectory {:?}, err{:?}, ignoring", + dent.file_name(), + e + ), + } + let _ = unlinkat(Some(dir_raw_fd), dent.file_name(), UnlinkatFlags::RemoveDir); + } else { + let _ = unlinkat( + Some(dir_raw_fd), + dent.file_name(), + UnlinkatFlags::NoRemoveDir, + ); + } + } +} + +fn cleanup_dir_after_db_cleanup(dir: &mut Dir, datadir: &Dir) { + let dir_raw_fd = dir.as_raw_fd(); + for entry in dir.iter() { + let dent = match entry { + Ok(dent) => dent, + Err(_) => continue, + }; + + if dot_or_dot_dot(dent.file_name().to_str().unwrap()) { + continue; + } + + if unsafe { + libc::faccessat( + datadir.as_raw_fd(), + dent.file_name().as_ptr(), + libc::F_OK, + libc::AT_SYMLINK_NOFOLLOW, + ) + } >= 0 + { + /* The corresponding udev database file still exists. + * Assuming the parsistent flag is set for the database. */ + continue; + } + + let _ = unlinkat( + Some(dir_raw_fd), + dent.file_name(), + UnlinkatFlags::NoRemoveDir, + ); + } +} diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..2203ba6fe1b17a8e505f8d26f7a936195d3f73a4 --- /dev/null +++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs @@ -0,0 +1,27 @@ +use device::Device; + +type Result = std::result::Result; + +pub fn find_device(id: &str, prefix: &str) -> Result { + if id.is_empty() { + return Err(nix::Error::EINVAL); + } + if let Ok(device) = Device::from_path(id) { + return Ok(device); + } + + if !prefix.is_empty() && !id.starts_with(prefix) { + let path = prefix.to_string() + id; + + if let Ok(device) = Device::from_path(&path) { + return Ok(device); + } + } + + find_device_from_unit(id) +} + +// dbus and device unit is not currently implemented +fn find_device_from_unit(_unit_name: &str) -> Result { + Err(nix::Error::EINVAL) +} diff --git a/exts/devmaster/src/bin/devctl/subcmds/mod.rs b/exts/devmaster/src/bin/devctl/subcmds/mod.rs index e1e5ad372665f48f79e560fd3fc18123d8141c63..3d79a88def567fd261075db9c5e47ca738d13c74 100644 --- a/exts/devmaster/src/bin/devctl/subcmds/mod.rs +++ b/exts/devmaster/src/bin/devctl/subcmds/mod.rs @@ -14,6 +14,8 @@ //! pub(crate) mod devctl_hwdb; +pub(crate) mod devctl_info; pub(crate) mod devctl_monitor; pub(crate) mod devctl_test_builtin; pub(crate) mod devctl_trigger; +pub(self) mod devctl_utils; diff --git a/exts/devmaster/src/lib/rules/node.rs b/exts/devmaster/src/lib/rules/node.rs index 85005ed7efd1c239a403c8797bc0d7f726736a5d..45b8b36b0d38847d4227820ca87fa64fd32e043a 100644 --- a/exts/devmaster/src/lib/rules/node.rs +++ b/exts/devmaster/src/lib/rules/node.rs @@ -35,7 +35,7 @@ use crate::{error::*, log_dev, log_dev_option}; use basic::fs_util::path_simplify; use basic::fs_util::{fchmod_and_chown, futimens_opath, symlink}; -use basic::{fd_util::opendirat, fs_util::remove_dir_until}; +use basic::{fd_util::xopendirat, fs_util::remove_dir_until}; use cluFlock::ExclusiveFlock; use device::Device; use libc::{mode_t, S_IFBLK, S_IFCHR, S_IFLNK, S_IFMT}; @@ -584,7 +584,7 @@ pub(crate) fn find_prioritized_devnode( dev: Rc>, dirfd: i32, ) -> Result> { - let mut dir = opendirat(dirfd, OFlag::O_NOFOLLOW) + let mut dir = xopendirat(dirfd, ".", OFlag::O_NOFOLLOW) .context(BasicSnafu) .log_error(&format!("failed to opendirat '{}'", dirfd))?; diff --git a/libs/basic/src/fd_util.rs b/libs/basic/src/fd_util.rs index 1e83ef8d03f60991a2c65f5d4273cb8c0a2af9a9..8715891cbb9b64b41ffb57485c3baff992756bc9 100644 --- a/libs/basic/src/fd_util.rs +++ b/libs/basic/src/fd_util.rs @@ -14,6 +14,7 @@ use crate::error::*; use libc::off_t; use nix::{ + dir::Dir, errno::Errno, fcntl::{openat, FcntlArg, FdFlag, OFlag}, ioctl_read, @@ -160,11 +161,15 @@ pub fn fd_get_diskseq(fd: RawFd) -> Result { Ok(diskseq) } -/// open the directory at fd -pub fn opendirat(dirfd: i32, flags: OFlag) -> Result { +/// open the directory at dirfd +pub fn xopendirat(dirfd: i32, name: &str, flags: OFlag) -> Result { + if dirfd == libc::AT_FDCWD && flags.is_empty() { + return Dir::open(name, flags, Mode::empty()).context(NixSnafu); + } + let nfd = openat( dirfd, - ".", + name, OFlag::O_RDONLY | OFlag::O_NONBLOCK | OFlag::O_DIRECTORY | OFlag::O_CLOEXEC | flags, Mode::empty(), ) @@ -183,6 +188,10 @@ pub fn file_offset_beyond_memory_size(x: off_t) -> bool { x as u64 > usize::MAX as u64 } +/// "." or ".." directory +pub fn dot_or_dot_dot(name: &str) -> bool { + name == "." || name == ".." +} #[cfg(test)] mod tests { use crate::fd_util::{stat_is_char, stat_is_reg}; @@ -196,7 +205,7 @@ mod tests { path::Path, }; - use super::opendirat; + use super::{dot_or_dot_dot, xopendirat}; #[test] fn test_stats() { @@ -228,7 +237,7 @@ mod tests { File::create("/tmp/test_opendirat/entry1").unwrap(); let dirfd = open("/tmp/test_opendirat", OFlag::O_DIRECTORY, Mode::empty()).unwrap(); - let mut dir = opendirat(dirfd, OFlag::O_NOFOLLOW).unwrap(); + let mut dir = xopendirat(dirfd, ".", OFlag::O_NOFOLLOW).unwrap(); for e in dir.iter() { let _ = e.unwrap(); @@ -236,4 +245,11 @@ mod tests { remove_dir_all("/tmp/test_opendirat").unwrap(); } + + #[test] + fn test_dot_or_dot_dot() { + assert!(dot_or_dot_dot(".")); + assert!(dot_or_dot_dot("..")); + assert!(!dot_or_dot_dot("/")); + } } diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs index a8c01075ba6aa9c97d9f29ba3c6b17d735242e63..98056f601d2bddb81bbecb7279ef76272b2de51d 100644 --- a/libs/device/src/device.rs +++ b/libs/device/src/device.rs @@ -18,8 +18,8 @@ use crate::{error::*, DeviceAction}; use basic::fs_util::{open_temporary, touch_file}; use basic::parse::{device_path_parse_devnum, parse_devnum, parse_ifindex}; use libc::{ - c_char, 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, + 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, }; use nix::dir::Dir; use nix::errno::{self, Errno}; @@ -30,6 +30,7 @@ use snafu::ResultExt; use std::cell::{Ref, RefCell}; use std::collections::hash_set::Iter; use std::collections::{HashMap, HashSet, VecDeque}; +use std::ffi::CString; use std::fs::{self, rename, OpenOptions, ReadDir}; use std::fs::{create_dir_all, File}; use std::io::{Read, Write}; @@ -2458,11 +2459,11 @@ impl Device { let property_tags_outdated = *self.property_tags_outdated.borrow(); if property_tags_outdated { - let all_tags: String = { + let mut all_tags: String = { let all_tags = self.all_tags.borrow(); - let tags_vec = all_tags.iter().map(|s| s.as_str()).collect::>(); - tags_vec.join(":") + all_tags.iter().map(|s| format!(":{}", s)).collect() }; + all_tags.push(':'); if !all_tags.is_empty() { self.add_property_internal("TAGS", &all_tags) @@ -2472,11 +2473,11 @@ impl Device { })?; } - let current_tags: String = { + let mut current_tags: String = { let current_tags = self.current_tags.borrow(); - let tags_vec = current_tags.iter().map(|s| s.as_str()).collect::>(); - tags_vec.join(":") + current_tags.iter().map(|s| format!(":{}", s)).collect() }; + current_tags.push(':'); if !current_tags.is_empty() { self.add_property_internal("CURRENT_TAGS", ¤t_tags) @@ -2900,9 +2901,16 @@ impl Device { }; if !subdir.is_empty() { - if unsafe { faccessat(dir.as_raw_fd(), "uevent".as_ptr() as *const c_char, F_OK, 0) } - >= 0 - { + let uevent_str = match CString::new("uevent") { + Ok(uevent_str) => uevent_str, + Err(e) => { + return Err(Error::Nix { + msg: format!("failed to new CString({:?}) '{}'", "uevent", e), + source: nix::Error::EINVAL, + }) + } + }; + if unsafe { faccessat(dir.as_raw_fd(), uevent_str.as_ptr(), F_OK, 0) } >= 0 { /* skip child device */ return Ok(()); } diff --git a/libs/device/src/device_enumerator.rs b/libs/device/src/device_enumerator.rs index b90a9a45c9383cc2c19d71bdea41c3a27339cf3d..ab796ac24a99f0e4f9d28c5bf64ad67345bfe129 100644 --- a/libs/device/src/device_enumerator.rs +++ b/libs/device/src/device_enumerator.rs @@ -1159,7 +1159,7 @@ impl DeviceEnumerator { } /// scan devices - pub(crate) fn scan_devices(&mut self) -> Result<(), Error> { + pub fn scan_devices(&mut self) -> Result<(), Error> { if *self.scan_up_to_date.borrow() && *self.etype.borrow() == DeviceEnumerationType::Devices { return Ok(());