sysmaster/backport-feature-devctl-add-devctl-info.patch

1094 lines
34 KiB
Diff
Raw Normal View History

2023-12-07 00:19:38 +08:00
From abc6eb6912fa8113ebe6238d95edbe920c703a30 Mon Sep 17 00:00:00 2001
From: huyubiao <huyubiao@huawei.com>
Date: Sun, 22 Oct 2023 14:42:13 +0800
Subject: [PATCH 004/103] feature(devctl): add devctl info
---
exts/devmaster/src/bin/devctl/main.rs | 91 ++-
.../src/bin/devctl/subcmds/devctl_info.rs | 680 ++++++++++++++++++
.../src/bin/devctl/subcmds/devctl_utils.rs | 29 +
exts/devmaster/src/bin/devctl/subcmds/mod.rs | 2 +
exts/devmaster/src/lib/rules/node.rs | 4 +-
libs/basic/src/fd_util.rs | 26 +-
libs/device/src/device.rs | 30 +-
libs/device/src/device_enumerator.rs | 2 +-
8 files changed, 838 insertions(+), 26 deletions(-)
create mode 100644 exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs
create mode 100644 exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs
diff --git a/exts/devmaster/src/bin/devctl/main.rs b/exts/devmaster/src/bin/devctl/main.rs
index 5fac776b..56d6d3b4 100644
--- a/exts/devmaster/src/bin/devctl/main.rs
+++ b/exts/devmaster/src/bin/devctl/main.rs
@@ -23,10 +23,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<T> = std::result::Result<T, nix::Error>;
+
/// parse program arguments
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
@@ -39,16 +42,63 @@ struct Args {
/// Kinds of subcommands
#[derive(Parser, Debug)]
enum SubCmd {
- /// Monitor device events from kernel and userspace
+ /// Query sysfs or the devmaster database
#[clap(display_order = 1)]
+ Info {
+ #[clap(short, long, possible_values(&["name", "symlink", "path", "property", "env", "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<String>,
+
+ /// 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<String>,
+
+ /// Export key/value pairs
+ #[clap(short('x'), long)]
+ export: bool,
+
+ /// Export the key name with a prefix
+ #[clap(short('P'), long)]
+ export_prefix: Option<String>,
+
+ /// 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<String>,
+ },
+
+ /// 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)]
@@ -72,7 +122,7 @@ enum SubCmd {
},
/// Test builtin command on a device
- #[clap(display_order = 4)]
+ #[clap(display_order = 5)]
TestBuiltin {
/// device action
#[clap(short, long)]
@@ -85,7 +135,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)]
@@ -114,16 +164,41 @@ fn subcommand_kill() {
stream.write_all(b"kill ").unwrap();
}
-fn main() {
+fn main() -> Result<()> {
let argv: Vec<String> = std::env::args().collect();
if invoked_as(argv, "devmaster") {
- return run_daemon();
+ run_daemon();
+ return Ok(());
}
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 {
@@ -147,4 +222,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 00000000..0fa76751
--- /dev/null
+++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs
@@ -0,0 +1,680 @@
+// 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<T> = std::result::Result<T, nix::Error>;
+
+#[derive(Debug)]
+enum QueryType {
+ Name,
+ Path,
+ Symlink,
+ Property,
+ All,
+}
+
+struct QueryProperty {
+ export: bool,
+ export_prefix: Option<String>,
+}
+
+impl QueryProperty {
+ fn new(export: bool, export_prefix: Option<String>) -> Self {
+ QueryProperty {
+ export,
+ export_prefix,
+ }
+ }
+}
+
+struct SysAttr {
+ name: String,
+ value: String,
+}
+
+pub struct InfoArgs {
+ query: Option<String>,
+ attribute_walk: bool,
+ device_id_of_file: Option<String>,
+ export: bool,
+ export_prefix: Option<String>,
+ export_db: bool,
+ cleanup_db: bool,
+ root: bool,
+ devices: Vec<String>,
+}
+
+impl InfoArgs {
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ query: Option<String>,
+ attribute_walk: bool,
+ device_id_of_file: Option<String>,
+ export: bool,
+ export_prefix: Option<String>,
+ export_db: bool,
+ cleanup_db: bool,
+ root: bool,
+ devices: Vec<String>,
+ ) -> 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, arg_export);
+ }
+
+ 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, export: bool) -> 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 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\
+ Devctl 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 devmaster 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<SysAttr> = 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;
+ }
+
+ /* skip nonprintable attributes */
+ 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/devmaster/data", OFlag::O_DIRECTORY, Mode::empty()) {
+ cleanup_dir(&mut dir1, libc::S_ISVTX, 1);
+
+ if let Ok(mut dir2) = Dir::open("/run/devmaster/links", OFlag::O_DIRECTORY, Mode::empty()) {
+ cleanup_dirs_after_db_cleanup(&mut dir2, &dir1);
+ }
+
+ if let Ok(mut dir3) = Dir::open("/run/devmaster/tags", OFlag::O_DIRECTORY, Mode::empty()) {
+ cleanup_dirs_after_db_cleanup(&mut dir3, &dir1);
+ }
+ }
+
+ /* static_node-tags is not currently implemented */
+ if let Ok(mut dir) = Dir::open(
+ "/run/devmaster/static_node-tags",
+ OFlag::O_DIRECTORY,
+ Mode::empty(),
+ ) {
+ cleanup_dir(&mut dir, 0, 2);
+ }
+
+ /* Do not remove /run/devmaster/watch. It will be handled by devmaster well on restart.
+ * And should not be removed by external program when devmaster is running. */
+
+ 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,
+ );
+ }
+ }
+}
+
+/*
+ * Assume that dir is a directory with file names matching devmaster data base
+ * entries for devices in /run/devmaster/data (such as "b8:16"), and removes
+ * all files except those that haven't been deleted in /run/devmaster/data
+ * (i.e. they were skipped during db cleanup because of the db_persist flag).
+ */
+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 devmaster database file still exists.
+ * Assuming the persistent 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 00000000..63b7a98e
--- /dev/null
+++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_utils.rs
@@ -0,0 +1,29 @@
+use device::Device;
+
+type Result<T> = std::result::Result<T, nix::Error>;
+
+/// find device by path or unit name
+pub fn find_device(id: &str, prefix: &str) -> Result<Device> {
+ 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);
+ }
+ }
+
+ /* Check if the argument looks like a device unit name. */
+ find_device_from_unit(id)
+}
+
+/// dbus and device unit is not currently implemented
+fn find_device_from_unit(_unit_name: &str) -> Result<Device> {
+ todo!()
+}
diff --git a/exts/devmaster/src/bin/devctl/subcmds/mod.rs b/exts/devmaster/src/bin/devctl/subcmds/mod.rs
index e1e5ad37..3d79a88d 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 85005ed7..45b8b36b 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<RefCell<Device>>,
dirfd: i32,
) -> Result<Option<String>> {
- 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 1e83ef8d..8715891c 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<u64> {
Ok(diskseq)
}
-/// open the directory at fd
-pub fn opendirat(dirfd: i32, flags: OFlag) -> Result<nix::dir::Dir> {
+/// open the directory at dirfd
+pub fn xopendirat(dirfd: i32, name: &str, flags: OFlag) -> Result<nix::dir::Dir> {
+ 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 fe1795c0..5b1eff1f 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,13 +2459,13 @@ 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::<Vec<_>>();
- tags_vec.join(":")
+ all_tags.iter().map(|s| format!(":{}", s)).collect()
};
if !all_tags.is_empty() {
+ all_tags.push(':');
self.add_property_internal("TAGS", &all_tags)
.map_err(|e| Error::Nix {
msg: format!("properties_prepare failed: {}", e),
@@ -2472,13 +2473,13 @@ 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::<Vec<_>>();
- tags_vec.join(":")
+ current_tags.iter().map(|s| format!(":{}", s)).collect()
};
if !current_tags.is_empty() {
+ current_tags.push(':');
self.add_property_internal("CURRENT_TAGS", &current_tags)
.map_err(|e| Error::Nix {
msg: format!("properties_prepare failed: {}", e),
@@ -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 b0f98655..38956cf5 100644
--- a/libs/device/src/device_enumerator.rs
+++ b/libs/device/src/device_enumerator.rs
@@ -1143,7 +1143,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(());
--
2.33.0