diff --git a/backport-feature-add-scsi_id.patch b/backport-feature-add-scsi_id.patch new file mode 100644 index 0000000..f28a8ee --- /dev/null +++ b/backport-feature-add-scsi_id.patch @@ -0,0 +1,1612 @@ +From cc238ef5521071f649affa2e029462a14e5af76a Mon Sep 17 00:00:00 2001 +From: jcg +Date: Wed, 13 Sep 2023 19:42:22 +0800 +Subject: [PATCH 002/103] feature: add scsi_id + +--- + exts/devmaster/Cargo.toml | 4 + + exts/devmaster/src/bin/tools/scsi_id/main.rs | 1577 ++++++++++++++++++ + 2 files changed, 1581 insertions(+) + create mode 100644 exts/devmaster/src/bin/tools/scsi_id/main.rs + +diff --git a/exts/devmaster/Cargo.toml b/exts/devmaster/Cargo.toml +index edd73ca2..c52e9880 100644 +--- a/exts/devmaster/Cargo.toml ++++ b/exts/devmaster/Cargo.toml +@@ -13,6 +13,10 @@ path = "src/bin/devctl/main.rs" + name = "ata_id" + path = "src/bin/tools/ata_id/main.rs" + ++[[bin]] ++name = "scsi_id" ++path = "src/bin/tools/scsi_id/main.rs" ++ + [lib] + name = "libdevmaster" + path = "src/lib/lib.rs" +diff --git a/exts/devmaster/src/bin/tools/scsi_id/main.rs b/exts/devmaster/src/bin/tools/scsi_id/main.rs +new file mode 100644 +index 00000000..8098c8bf +--- /dev/null ++++ b/exts/devmaster/src/bin/tools/scsi_id/main.rs +@@ -0,0 +1,1577 @@ ++// 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. ++ ++//! scsi_id - retrieve and generate a unique identifier ++//! ++ ++use basic::IN_SET; ++use libc::{c_char, c_void, getopt_long, option, EBADF, EINVAL, ENOSYS}; ++use libdevmaster::utils::commons::{encode_devnode_name, replace_chars, replace_whitespace}; ++use nix::{ ++ errno::{self, Errno}, ++ fcntl::{open, OFlag}, ++ sys::stat::{self, fstat, major, minor, Mode}, ++}; ++use scsi_generic_rs::{sg_io_hdr, sg_io_v4, SG_IO}; ++use std::{ ++ ffi::{CStr, CString}, ++ fs::File, ++ io::{BufRead, BufReader}, ++ os::{ ++ raw::{c_int, c_uchar, c_uint, c_ulonglong}, ++ unix::prelude::FromRawFd, ++ }, ++ process::exit, ++ ptr::NonNull, ++ str::from_utf8, ++ thread, ++ time::Duration, ++}; ++ ++extern "C" { ++ ///option arg for getopt_long ++ pub static mut optarg: *mut c_char; ++ ///option index for getopt_long ++ pub static mut optind: c_int; ++} ++ ++///device in whitelist or blacklist ++pub static mut ALL_GOOD: bool = false; ++///device specified ++pub static mut DEV_SPECIFIED: bool = false; ++///config file for scsi_id ++pub static mut SCSI_ID_CONFIG: &str = "/etc/scsi_id.config"; ++///devnode name ++pub static mut MAJ_MIN_DEV: String = String::new(); ++///default pagecode ++pub static mut DEFAULT_PAGE_CODE: PageCode = PageCode::PageUnspecified; ++///default SCSI Generic version ++pub static mut DEFAULT_SG_VERSION: u8 = 4; ++///replace whitespace ++pub static mut REFORMAT_SERIAL: bool = false; ++///print values ++pub static mut EXPORT: bool = false; ++///vendor name ++pub static mut VENDOR: String = String::new(); ++///encoded vendor name ++pub static mut VENDOR_ENC_STR: String = String::new(); ++///model name ++pub static mut MODEL: String = String::new(); ++///encoded model name ++pub static mut MODEL_ENC_STR: String = String::new(); ++///revision get from ioctl ++pub static mut REVISION: String = String::new(); ++///device type get from ioctl ++pub static mut TYPE_STR: String = String::new(); ++///argc get from scsi_id.conf ++pub static mut ARGC: i32 = 0; ++///argv get from scsi_id.conf ++pub static mut ARGV: Vec = Vec::new(); ++///default timeout for ioctl ++pub const DEF_TIMEOUT: u32 = 5000; ++///length of SENSE_BUFF ++pub const SENSE_BUFF_LEN: u32 = 32; ++///length of SCSI_INQ_BUFF ++pub const SCSI_INQ_BUFF_LEN: u32 = 254; ++///max length for vendor ++pub const VENDOR_LENGTH: usize = 8; ++///max length for model ++pub const MODEL_LENGTH: usize = 16; ++///inquiry cmd for ioctl ++pub const INQUIRY_CMD: c_uchar = 0x12; ++///inquiry cmd length for ioctl ++pub const INQUIRY_CMDLEN: u32 = 6; ++///id type values of id descriptors ++pub const SCSI_ID_VENDOR_SPECIFIC: c_uchar = 0; ++///id type values of id descriptors ++pub const SCSI_ID_T10_VENDOR: c_uchar = 1; ++///id type values of id descriptors ++pub const SCSI_ID_EUI_64: c_uchar = 2; ++///id type values of id descriptors ++pub const SCSI_ID_NAA: c_uchar = 3; ++///id type values of id descriptors ++pub const SCSI_ID_RELPORT: c_uchar = 4; ++///id type values of id descriptors ++pub const SCSI_ID_TGTGROUP: c_uchar = 5; ++///naa type values of id descriptors ++pub const SCSI_ID_NAA_DONT_CARE: c_uchar = 0xff; ++///naa type values of id descriptors ++pub const SCSI_ID_NAA_IEEE_REG: c_uchar = 0x05; ++///naa type values of id descriptors ++pub const SCSI_ID_NAA_IEEE_REG_EXTENDED: c_uchar = 0x06; ++///code set values of id descriptors ++pub const SCSI_ID_BINARY: c_uchar = 1; ++///code set values of id descriptors ++pub const SCSI_ID_ASCII: c_uchar = 2; ++///SCSI status codes ++pub const SCSI_CHECK_CONDITION: c_uchar = 0x02; ++///SCSI status codes ++pub const SCSI_COMMAND_TERMINATED: c_uchar = 0x22; ++///unable to connect before timeout ++pub const DID_NO_CONNECT: u32 = 0x01; ++///bus remain busy until timeout ++pub const DID_BUS_BUSY: u32 = 0x02; ++///timed out for some other reason ++pub const DID_TIME_OUT: u32 = 0x03; ++///transport disrupted and should retry ++pub const DID_TRANSPORT_DISPUPTED: u32 = 0x0e; ++///driver status ++pub const DRIVER_TIMEOUT: u32 = 0x06; ++///sense_buffer has been set ++pub const DRIVER_SENSE: u32 = 0x08; ++///no errors or other information ++pub const SG_ERR_CAT_CLEAN: i32 = 0; ++///interpreted from sense buffer ++pub const SG_ERR_CAT_MEDIA_CHANGED: i32 = 1; ++///interpreted from sense buffer ++pub const SG_ERR_CAT_RESET: i32 = 2; ++///timeout to get sense buffer ++pub const SG_ERR_CAT_TIMEOUT: i32 = 3; ++///successful command after recovered err ++pub const SG_ERR_CAT_RECOVERED: i32 = 4; ++///illegal / unsupported command ++pub const SG_ERR_CAT_NOTSUPPORTED: i32 = 5; ++///illegal / unsupported command ++pub const SG_ERR_CAT_RETRY: i32 = 6; ++///something else in the sense buffer ++pub const SG_ERR_CAT_SENSE: i32 = 98; ++///some other error/warning ++pub const SG_ERR_CAT_OTHER: i32 = 99; ++///subprotocol of io_v4 ++pub const BSG_PROTOCOL_SCSI: u32 = 0; ++///subprotocol of io_v4 ++pub const BSG_SUB_PROTOCOL_SCSI_CMD: u32 = 0; ++///dxfer direction of io_hdr ++pub const SG_DXFER_FROM_DEV: i32 = -3; ++///sense key ++pub const RECOVERED_ERROR: u64 = 0x01; ++///sense key ++pub const ILLEGAL_REQUEST: u64 = 0x05; ++///sense key ++pub const UNIT_ATTENTION: u64 = 0x06; ++///max length for serial ++pub const MAX_SERIAL_LEN: i32 = 256; ++ ++#[allow(missing_docs)] ++#[derive(Debug)] ++pub enum PageCode { ++ Page83PreSpc3 = -0x83, ++ PageUnspecified = 0x00, ++ Page80 = 0x80, ++ Page83 = 0x83, ++} ++ ++impl std::fmt::Display for PageCode { ++ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { ++ match self { ++ PageCode::Page83PreSpc3 => write!(f, "-{:#04x}", 0x83), ++ PageCode::PageUnspecified => write!(f, "{:#04x}", 0x00), ++ PageCode::Page80 => write!(f, "{:#04x}", 0x80), ++ PageCode::Page83 => write!(f, "{:#04x}", 0x83), ++ } ++ } ++} ++ ++#[allow(missing_docs)] ++#[derive(Default, Debug)] ++pub struct ScsiIdDevice { ++ vendor: String, ++ model: String, ++ revision: String, ++ kernel: String, ++ serial: String, ++ serial_short: String, ++ r#type: u8, ++ use_sg: u8, ++ unit_serial_number: String, ++ wwn: String, ++ wwn_vendor_extension: String, ++ tgpt_group: String, ++} ++ ++#[repr(C)] ++#[allow(non_camel_case_types)] ++#[allow(missing_docs)] ++#[derive(Debug, Copy, Clone)] ++pub struct scsi_id_search_values { ++ id_type: c_uchar, ++ naa_type: c_uchar, ++ code_set: c_uchar, ++} ++#[allow(missing_docs)] ++pub static ID_SEARCH_LIST: [scsi_id_search_values; 13] = [ ++ scsi_id_search_values { ++ id_type: SCSI_ID_TGTGROUP, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_IEEE_REG_EXTENDED, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_IEEE_REG_EXTENDED, ++ code_set: SCSI_ID_ASCII, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_IEEE_REG, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_IEEE_REG, ++ code_set: SCSI_ID_ASCII, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_NAA, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_ASCII, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_EUI_64, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_EUI_64, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_ASCII, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_T10_VENDOR, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_T10_VENDOR, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_ASCII, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_VENDOR_SPECIFIC, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_BINARY, ++ }, ++ scsi_id_search_values { ++ id_type: SCSI_ID_VENDOR_SPECIFIC, ++ naa_type: SCSI_ID_NAA_DONT_CARE, ++ code_set: SCSI_ID_ASCII, ++ }, ++]; ++ ++fn set_options(argc: i32, argv: Vec) -> i32 { ++ let mut args_c: Vec<*mut c_char> = Vec::new(); ++ for option in &argv { ++ args_c.push(CString::new(option.clone()).unwrap().into_raw()); ++ } ++ let device = CString::new("device").unwrap(); ++ let config = CString::new("config").unwrap(); ++ let page = CString::new("page").unwrap(); ++ let denylisted = CString::new("denylisted").unwrap(); ++ let allowlisted = CString::new("allowlisted").unwrap(); ++ let blacklisted = CString::new("blacklisted").unwrap(); ++ let whitelisted = CString::new("whitelisted").unwrap(); ++ let replace_whitespace = CString::new("replace-whitespace").unwrap(); ++ let sg_version = CString::new("sg-version").unwrap(); ++ let verbose = CString::new("verbose").unwrap(); ++ let version = CString::new("version").unwrap(); ++ let export = CString::new("export").unwrap(); ++ let help = CString::new("help").unwrap(); ++ ++ let longopts = [ ++ option { ++ name: device.as_ptr(), ++ has_arg: 1, ++ flag: std::ptr::null_mut(), ++ val: b'd' as c_int, ++ }, ++ option { ++ name: config.as_ptr(), ++ has_arg: 1, ++ flag: std::ptr::null_mut(), ++ val: b'f' as c_int, ++ }, ++ option { ++ name: page.as_ptr(), ++ has_arg: 1, ++ flag: std::ptr::null_mut(), ++ val: b'p' as c_int, ++ }, ++ option { ++ name: denylisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'b' as c_int, ++ }, ++ option { ++ name: allowlisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'g' as c_int, ++ }, ++ option { ++ name: blacklisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'b' as c_int, ++ }, ++ option { ++ name: whitelisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'g' as c_int, ++ }, ++ option { ++ name: replace_whitespace.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'u' as c_int, ++ }, ++ option { ++ name: sg_version.as_ptr(), ++ has_arg: 1, ++ flag: std::ptr::null_mut(), ++ val: b's' as c_int, ++ }, ++ option { ++ name: verbose.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'v' as c_int, ++ }, ++ option { ++ name: version.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'V' as c_int, ++ }, ++ option { ++ name: export.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'x' as c_int, ++ }, ++ option { ++ name: help.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'h' as c_int, ++ }, ++ option { ++ name: std::ptr::null_mut(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: 0, ++ }, ++ ]; ++ unsafe { ++ optind = 1; ++ let optstring = CString::new("d:f:gp:uvVxhbs:").unwrap(); ++ ++ loop { ++ let c = getopt_long( ++ argc as c_int, ++ args_c.as_ptr() as *const *mut c_char, ++ optstring.as_ptr(), ++ longopts.as_ptr(), ++ &mut 0, ++ ); ++ ++ if c == -1 { ++ break; ++ } ++ ++ match c as u8 as char { ++ 'b' => ALL_GOOD = false, ++ 'd' => { ++ DEV_SPECIFIED = true; ++ let optarg_tmp = CStr::from_ptr(optarg as *const c_char); ++ MAJ_MIN_DEV = from_utf8(optarg_tmp.to_bytes()).unwrap().to_string(); ++ } ++ 'f' => { ++ let optarg_tmp = CStr::from_ptr(optarg as *const c_char); ++ SCSI_ID_CONFIG = optarg_tmp.to_str().unwrap(); ++ } ++ 'g' => ALL_GOOD = true, ++ 'h' => { ++ println!("Usage: scsi_id [OPTION...] DEVICE"); ++ println!("SCSI device identification."); ++ println!(); ++ println!(" -h --help Print this message"); ++ println!(" -V --version Print version of the program"); ++ println!(" -d --device= Device node for SG_IO commands"); ++ println!(" -f --config= Location of config file"); ++ println!( ++ " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)" ++ ); ++ println!(" -s --sg-version=3|4 Use SGv3 or SGv4"); ++ println!(" -b --blacklisted Treat device as blacklist"); ++ println!(" -g --whitelisted Treat device as whitelisted"); ++ println!( ++ " -u --replace-whitespace Replace all whitespace by underscores" ++ ); ++ println!(" -x --export Print values as environment keys"); ++ exit(0); ++ } ++ 'p' => { ++ let optarg_tmp = CStr::from_ptr(optarg as *const c_char); ++ let pagecode = optarg_tmp.to_str().unwrap(); ++ match pagecode { ++ "0x80" => DEFAULT_PAGE_CODE = PageCode::Page80, ++ "0x83" => DEFAULT_PAGE_CODE = PageCode::Page83, ++ "pre-spc3-83" => DEFAULT_PAGE_CODE = PageCode::Page83PreSpc3, ++ _ => { ++ log::error!("unknown page code."); ++ return -1; ++ } ++ } ++ } ++ 's' => { ++ let optarg_tmp = CStr::from_ptr(optarg as *const c_char); ++ let sg_version_str = optarg_tmp.to_str().unwrap(); ++ DEFAULT_SG_VERSION = sg_version_str.parse().unwrap(); ++ if !(3..=4).contains(&DEFAULT_SG_VERSION) { ++ return -1; ++ } ++ } ++ 'u' => REFORMAT_SERIAL = true, ++ 'V' => { ++ println!("scsi_id {}", env!("CARGO_PKG_VERSION")); ++ return 0; ++ } ++ 'x' => EXPORT = true, ++ _ => { ++ println!("invalid arguments"); ++ return -1; ++ } ++ } ++ } ++ ++ if optind < argc && !DEV_SPECIFIED { ++ DEV_SPECIFIED = true; ++ let tmp = CStr::from_ptr(args_c[optind as usize] as *const c_char); ++ MAJ_MIN_DEV = from_utf8(tmp.to_bytes()).unwrap().to_string(); ++ } ++ } ++ ++ 0 ++} ++ ++fn sg_err_category_new( ++ mut scsi_status: u32, ++ host_status: u32, ++ driver_status: u32, ++ sense: u64, ++ sb_len: u32, ++) -> i32 { ++ scsi_status &= 0x7e; ++ if scsi_status == 0 && host_status == 0 && driver_status == 0 { ++ return SG_ERR_CAT_CLEAN; ++ } ++ ++ if IN_SET!( ++ scsi_status, ++ SCSI_CHECK_CONDITION as u32, ++ SCSI_COMMAND_TERMINATED as u32 ++ ) || (driver_status & 0xf) == DRIVER_SENSE ++ { ++ let sense_buffer; ++ let non_null_ptr = NonNull::new(sense as *mut u64); ++ match non_null_ptr { ++ Some(sense) => { ++ sense_buffer = unsafe { ++ std::slice::from_raw_parts(sense.as_ptr() as *mut u64, SENSE_BUFF_LEN as usize) ++ }; ++ } ++ None => { ++ return SG_ERR_CAT_SENSE; ++ } ++ } ++ if sb_len > 2 { ++ let sense_key; ++ let asc; ++ ++ if (sense_buffer[0] & 0x2) != 0 { ++ sense_key = sense_buffer[1] & 0xf; ++ asc = sense_buffer[2]; ++ } else { ++ sense_key = sense_buffer[2] & 0xf; ++ if sb_len > 12 { ++ asc = sense_buffer[12]; ++ } else { ++ asc = 0; ++ } ++ } ++ ++ if sense_key == RECOVERED_ERROR { ++ return SG_ERR_CAT_RECOVERED; ++ } else if sense_key == UNIT_ATTENTION { ++ if asc == 0x28 { ++ return SG_ERR_CAT_MEDIA_CHANGED; ++ } ++ ++ if asc == 0x29 { ++ return SG_ERR_CAT_RESET; ++ } ++ } else if sense_key == ILLEGAL_REQUEST { ++ return SG_ERR_CAT_NOTSUPPORTED; ++ } ++ return SG_ERR_CAT_SENSE; ++ } ++ if host_status != 0 { ++ if IN_SET!(host_status, DID_NO_CONNECT, DID_BUS_BUSY, DID_TIME_OUT) { ++ return SG_ERR_CAT_TIMEOUT; ++ } ++ ++ if host_status == DID_TRANSPORT_DISPUPTED { ++ return SG_ERR_CAT_RETRY; ++ } ++ } ++ ++ if driver_status != 0 && driver_status == DRIVER_TIMEOUT { ++ return SG_ERR_CAT_TIMEOUT; ++ } ++ } ++ SG_ERR_CAT_OTHER ++} ++ ++fn sg_err_catagory4(io_buf_v4: &mut sg_io_v4) -> i32 { ++ sg_err_category_new( ++ io_buf_v4.device_status, ++ io_buf_v4.transport_status, ++ io_buf_v4.device_status, ++ io_buf_v4.response, ++ io_buf_v4.request_len, ++ ) ++} ++ ++fn sg_err_catagory3(io_buf_hdr: &mut sg_io_hdr) -> i32 { ++ sg_err_category_new( ++ io_buf_hdr.status.into(), ++ io_buf_hdr.host_status.into(), ++ io_buf_hdr.driver_status.into(), ++ io_buf_hdr.sbp as u64, ++ io_buf_hdr.sb_len_wr.into(), ++ ) ++} ++ ++fn scsi_dump_sense(dev_scsi: &mut ScsiIdDevice, sense: u64, sb_len: u32) -> i32 { ++ if sb_len < 1 { ++ log::debug!("{}: sense buffer empty.", dev_scsi.kernel); ++ return -EINVAL; ++ } ++ let sense_buffer; ++ let non_null_ptr = NonNull::new(sense as *mut u64); ++ match non_null_ptr { ++ Some(sense) => { ++ sense_buffer = ++ unsafe { std::slice::from_raw_parts(sense.as_ptr() as *mut u64, sb_len as usize) }; ++ } ++ None => { ++ return -EINVAL; ++ } ++ } ++ let s; ++ let sense_key; ++ let asc; ++ let ascq; ++ ++ let sense_class = sense_buffer[0] >> 4 & 0x07; ++ let code = sense_buffer[0] & 0xf; ++ ++ if sense_class == 7 { ++ s = sense_buffer[7] as u32 + 8; ++ if sb_len < s { ++ log::debug!( ++ "{}: sense buffer too small {} bytes, {} bytes too short.", ++ dev_scsi.kernel, ++ sb_len, ++ s - sb_len ++ ); ++ return -EINVAL; ++ } ++ ++ if IN_SET!(code, 0x0, 0x1) { ++ sense_key = sense_buffer[2] & 0xf; ++ if s < 14 { ++ log::debug!("{}: sense result too small {} bytes", dev_scsi.kernel, s); ++ return -EINVAL; ++ } ++ asc = sense_buffer[12]; ++ ascq = sense_buffer[13]; ++ } else if IN_SET!(code, 0x2, 0x3) { ++ sense_key = sense_buffer[1] & 0xf; ++ asc = sense_buffer[2]; ++ ascq = sense_buffer[3]; ++ } else { ++ log::debug!("{}: invalid sense code {:x}.", dev_scsi.kernel, code); ++ return -EINVAL; ++ } ++ log::debug!( ++ "{}: sense key {:x} ASC {:x} ASCQ {:x}.", ++ dev_scsi.kernel, ++ sense_key, ++ asc, ++ ascq ++ ); ++ } else { ++ if sb_len < 4 { ++ log::debug!( ++ "{}: sense buffer too small {} bytes, {} bytes too short.", ++ dev_scsi.kernel, ++ sb_len, ++ 4 - sb_len ++ ); ++ return -EINVAL; ++ } ++ ++ if sense_buffer[0] < 15 { ++ log::debug!( ++ "{}: old sense key: {:x}", ++ dev_scsi.kernel, ++ sense_buffer[0] & 0x0f ++ ); ++ } else { ++ log::debug!( ++ "{}: sense = {} {}.", ++ dev_scsi.kernel, ++ sense_buffer[0], ++ sense_buffer[2] ++ ); ++ } ++ log::debug!( ++ "{}: non-extended sense class {} code {:x}.", ++ dev_scsi.kernel, ++ sense_class, ++ code ++ ); ++ } ++ ++ -1 ++} ++ ++fn scsi_dump_v4(dev_scsi: &mut ScsiIdDevice, io_buf_v4: sg_io_v4) -> i32 { ++ if io_buf_v4.device_status != 0 ++ && io_buf_v4.transport_status != 0 ++ && io_buf_v4.driver_status != 0 ++ { ++ log::debug!("scsi_dump_v4: {}: sense buffer empty.", dev_scsi.kernel); ++ return -EINVAL; ++ } ++ ++ log::debug!( ++ "scsi_dump_v4: {}: sg_io failed status {:x} {:x} {:x}", ++ dev_scsi.kernel, ++ io_buf_v4.driver_status, ++ io_buf_v4.transport_status, ++ io_buf_v4.device_status ++ ); ++ if io_buf_v4.device_status == (SCSI_CHECK_CONDITION as u32) { ++ return scsi_dump_sense(dev_scsi, io_buf_v4.response, io_buf_v4.request_len); ++ } ++ ++ -1 ++} ++ ++fn scsi_dump(dev_scsi: &mut ScsiIdDevice, io_buf_hdr: sg_io_hdr) -> i32 { ++ if io_buf_hdr.status != 0 ++ && io_buf_hdr.host_status != 0 ++ && io_buf_hdr.msg_status != 0 ++ && io_buf_hdr.driver_status != 0 ++ { ++ log::debug!("scsi_dump: {}: sense buffer empty.", dev_scsi.kernel); ++ return -EINVAL; ++ } ++ ++ log::debug!( ++ "scsi_dump: {}: sg_io failed status {:x} {:x} {:x} {:x}", ++ dev_scsi.kernel, ++ io_buf_hdr.driver_status, ++ io_buf_hdr.host_status, ++ io_buf_hdr.msg_status, ++ io_buf_hdr.status ++ ); ++ if io_buf_hdr.status == (SCSI_CHECK_CONDITION as u8) { ++ return scsi_dump_sense(dev_scsi, io_buf_hdr.sbp as u64, io_buf_hdr.sb_len_wr as u32); ++ } ++ ++ -1 ++} ++ ++fn scsi_inquiry( ++ dev_scsi: &mut ScsiIdDevice, ++ fd: i32, ++ evpd: c_uchar, ++ page: c_uchar, ++ buffer: &mut [c_uchar], ++ buflen: u32, ++) -> i32 { ++ let inq_cmd: [c_uchar; INQUIRY_CMDLEN as usize] = ++ [INQUIRY_CMD, evpd, page, 0, buflen as c_uchar, 0]; ++ let sense = [0_u8; SENSE_BUFF_LEN as usize]; ++ let mut io_buf_v4 = sg_io_v4::default(); ++ let mut io_buf_hdr = sg_io_hdr::default(); ++ let mut r = 0; ++ let mut retry = 3; ++ ++ while retry > 0 { ++ if dev_scsi.use_sg == 4 { ++ io_buf_v4.guard = 'Q' as c_int; ++ io_buf_v4.protocol = BSG_PROTOCOL_SCSI; ++ io_buf_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; ++ io_buf_v4.request_len = INQUIRY_CMDLEN; ++ io_buf_v4.request = inq_cmd.as_ptr() as c_ulonglong; ++ io_buf_v4.max_response_len = SENSE_BUFF_LEN; ++ io_buf_v4.response = sense.as_ptr() as c_ulonglong; ++ io_buf_v4.din_xfer_len = buflen; ++ io_buf_v4.din_xferp = buffer.as_mut_ptr() as c_ulonglong; ++ ++ r = unsafe { libc::ioctl(fd, (SG_IO as i32).try_into().unwrap(), &io_buf_v4) }; ++ if r < 0 { ++ if IN_SET!(errno::errno(), EINVAL, ENOSYS) { ++ dev_scsi.use_sg = 3; ++ } else { ++ log::debug!("{}: ioctl failed for io_buf_v4!", dev_scsi.kernel); ++ return r; ++ } ++ } ++ } ++ ++ if dev_scsi.use_sg != 4 { ++ io_buf_hdr.interface_id = 'S' as c_int; ++ io_buf_hdr.cmd_len = INQUIRY_CMDLEN as c_uchar; ++ io_buf_hdr.mx_sb_len = SENSE_BUFF_LEN as c_uchar; ++ io_buf_hdr.dxfer_direction = SG_DXFER_FROM_DEV; ++ io_buf_hdr.dxfer_len = buflen; ++ io_buf_hdr.dxferp = buffer.as_mut_ptr() as *mut c_void; ++ io_buf_hdr.cmdp = inq_cmd.as_ptr() as *mut c_uchar; ++ io_buf_hdr.sbp = sense.as_ptr() as *mut c_uchar; ++ io_buf_hdr.timeout = DEF_TIMEOUT; ++ ++ r = unsafe { libc::ioctl(fd, (SG_IO as i32).try_into().unwrap(), &io_buf_hdr) }; ++ if r < 0 { ++ log::debug!("{}: ioctl failed for io_buf_hdr!", dev_scsi.kernel); ++ return r; ++ } ++ } ++ ++ if dev_scsi.use_sg == 4 { ++ r = sg_err_catagory4(&mut io_buf_v4); ++ } else { ++ r = sg_err_catagory3(&mut io_buf_hdr); ++ } ++ ++ match r { ++ SG_ERR_CAT_NOTSUPPORTED => { ++ buffer[1] = 0_u8; ++ r = 0; ++ } ++ SG_ERR_CAT_CLEAN | SG_ERR_CAT_RECOVERED => { ++ r = 0; ++ } ++ SG_ERR_CAT_RETRY => {} ++ _ => { ++ if dev_scsi.use_sg == 4 { ++ r = scsi_dump_v4(dev_scsi, io_buf_v4); ++ } else { ++ r = scsi_dump(dev_scsi, io_buf_hdr); ++ } ++ } ++ } ++ ++ if r == 0 { ++ r = buflen as i32; ++ break; ++ } ++ ++ if r > 0 && retry == 1 { ++ r = -1 ++ } ++ ++ retry -= 1; ++ } ++ r ++} ++ ++fn scsi_std_inquiry(dev_scsi: &mut ScsiIdDevice, devname: &str) -> i32 { ++ let mut buffer = [0_u8; SCSI_INQ_BUFF_LEN as usize]; ++ let fd = match open( ++ devname, ++ OFlag::O_RDONLY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC | OFlag::O_NOCTTY, ++ Mode::from_bits(0o666).unwrap(), ++ ) { ++ Ok(fd) => fd, ++ Err(e) => { ++ log::debug!("scsi_std_inquiry: Cannot open {}: {}", devname, e); ++ return e as i32; ++ } ++ }; ++ ++ let statbuf = match fstat(fd) { ++ Err(_) => { ++ log::debug!("scsi_std_inquiry: can't fstat {}", devname); ++ return 2; ++ } ++ Ok(stat) => stat, ++ }; ++ ++ dev_scsi.kernel = format!("{}:{}", major(statbuf.st_rdev), minor(statbuf.st_rdev)); ++ ++ let r = scsi_inquiry(dev_scsi, fd, 0, 0, &mut buffer, SCSI_INQ_BUFF_LEN); ++ if r < 0 { ++ return r; ++ } ++ ++ let tmp = ++ unsafe { std::slice::from_raw_parts(buffer.as_mut_ptr() as *mut c_uchar, buffer.len()) }; ++ dev_scsi.vendor = String::from_utf8(tmp[8..16].to_vec()).unwrap(); ++ dev_scsi.model = String::from_utf8(tmp[16..32].to_vec()).unwrap(); ++ dev_scsi.revision = String::from_utf8(tmp[32..36].to_vec()).unwrap(); ++ dev_scsi.r#type = tmp[0] as u8 & 0x1f; ++ ++ 0 ++} ++ ++fn set_inq_values(dev_scsi: &mut ScsiIdDevice, maj_min_dev: &str) -> i32 { ++ dev_scsi.use_sg = unsafe { DEFAULT_SG_VERSION }; ++ ++ let r = scsi_std_inquiry(dev_scsi, maj_min_dev); ++ if r != 0 { ++ return r; ++ } ++ ++ encode_devnode_name(&dev_scsi.vendor, unsafe { &mut VENDOR_ENC_STR }); ++ encode_devnode_name(&dev_scsi.model, unsafe { &mut MODEL_ENC_STR }); ++ ++ unsafe { ++ VENDOR = replace_whitespace(&dev_scsi.vendor); ++ VENDOR = replace_chars(&VENDOR, ""); ++ MODEL = replace_whitespace(&dev_scsi.model); ++ MODEL = replace_chars(&MODEL, ""); ++ REVISION = replace_whitespace(&dev_scsi.revision); ++ REVISION = replace_chars(&REVISION, ""); ++ }; ++ ++ unsafe { ++ TYPE_STR = match dev_scsi.r#type { ++ 0 | 0xe => "disk".to_string(), ++ 1 => "tape".to_string(), ++ 4 | 7 | 0xf => "optical".to_string(), ++ 5 => "cd".to_string(), ++ _ => "generic".to_string(), ++ } ++ } ++ ++ 0 ++} ++ ++fn get_file_options(vendor: String, model: String) -> i32 { ++ let r = 0; ++ let mut vendor_in = String::new(); ++ let mut model_in = String::new(); ++ let mut options_in = String::new(); ++ let file = match open( ++ unsafe { SCSI_ID_CONFIG }, ++ OFlag::O_RDONLY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC | OFlag::O_NOCTTY, ++ stat::Mode::empty(), ++ ) { ++ Ok(fd) => unsafe { File::from_raw_fd(fd) }, ++ Err(e) => { ++ return e as i32; ++ } ++ }; ++ ++ let reader: BufReader = BufReader::new(file); ++ for (lineno, lines) in reader.lines().enumerate() { ++ let line = match lines { ++ Ok(line) => line, ++ Err(e) => { ++ log::debug!("read line err: {}", e.to_string()); ++ return -1; ++ } ++ }; ++ ++ if line.clone().starts_with('#') { ++ continue; ++ } ++ ++ if line.clone().trim().is_empty() { ++ continue; ++ } ++ ++ let vec: Vec = line.split(',').map(|s| s.to_string()).collect(); ++ for v in vec { ++ let pair: Vec = v.split('=').map(|s| s.to_string()).collect(); ++ if pair.len() > 2 { ++ log::error!( ++ "failed to parse config file {} line {}", ++ unsafe { SCSI_ID_CONFIG }, ++ lineno ++ ); ++ return -1; ++ } ++ ++ if pair[0].to_lowercase().starts_with("vendor") { ++ vendor_in = pair[1].clone(); ++ } ++ ++ if pair[0].to_lowercase().starts_with("model") { ++ model_in = pair[1].clone(); ++ } ++ ++ if pair[0].to_lowercase().starts_with("options") { ++ options_in = pair[1].clone(); ++ } ++ } ++ ++ if options_in.is_empty() || (vendor_in.is_empty() && !model_in.is_empty()) { ++ log::error!( ++ "failed to parse config file {} line {}", ++ unsafe { SCSI_ID_CONFIG }, ++ lineno ++ ); ++ return -1; ++ } ++ ++ if vendor.is_empty() { ++ if vendor_in.is_empty() { ++ break; ++ } ++ } else if !vendor_in.is_empty() ++ && vendor.starts_with(&vendor_in) ++ && (model_in.is_empty() || model.starts_with(&model_in)) ++ { ++ break; ++ } ++ } ++ ++ if vendor_in.is_empty() && model_in.is_empty() && options_in.is_empty() { ++ log::error!("can't get any values from {}", unsafe { SCSI_ID_CONFIG }); ++ return -1; ++ } ++ unsafe { ++ ARGV = options_in.split(' ').map(|s| s.to_string()).collect(); ++ ARGV.insert(0, "".to_owned()); ++ ARGC = ARGV.len() as i32 ++ }; ++ r ++} ++ ++fn per_dev_options(dev_scsi: &mut ScsiIdDevice) -> i32 { ++ let newargc: i32 = 0; ++ let newargv: Vec = Vec::new(); ++ ++ let page = CString::new("page").unwrap(); ++ let denylisted = CString::new("denylisted").unwrap(); ++ let allowlisted = CString::new("allowlisted").unwrap(); ++ let blacklisted = CString::new("blacklisted").unwrap(); ++ let whitelisted = CString::new("whitelisted").unwrap(); ++ ++ let longopts = [ ++ option { ++ name: page.as_ptr(), ++ has_arg: 1, ++ flag: std::ptr::null_mut(), ++ val: b'p' as c_int, ++ }, ++ option { ++ name: denylisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'b' as c_int, ++ }, ++ option { ++ name: allowlisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'g' as c_int, ++ }, ++ option { ++ name: blacklisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'b' as c_int, ++ }, ++ option { ++ name: whitelisted.as_ptr(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: b'g' as c_int, ++ }, ++ option { ++ name: std::ptr::null_mut(), ++ has_arg: 0, ++ flag: std::ptr::null_mut(), ++ val: 0, ++ }, ++ ]; ++ ++ let mut r = get_file_options(dev_scsi.vendor.clone(), dev_scsi.model.clone()); ++ unsafe { optind = 1 }; ++ let optstring = CString::new("bgp:").unwrap(); ++ while r == 0 { ++ unsafe { ++ let o = getopt_long( ++ newargc, ++ newargv.as_ptr() as *const *mut c_char, ++ optstring.as_ptr(), ++ longopts.as_ptr(), ++ &mut 0, ++ ); ++ if o == -1 { ++ break; ++ } ++ match o as u8 as char { ++ 'b' => ALL_GOOD = false, ++ 'g' => ALL_GOOD = true, ++ 'p' => { ++ let optarg_tmp = CStr::from_ptr(optarg as *const c_char); ++ let pagecode = optarg_tmp.to_str().unwrap(); ++ match pagecode { ++ "0x80" => DEFAULT_PAGE_CODE = PageCode::Page80, ++ "0x83" => DEFAULT_PAGE_CODE = PageCode::Page83, ++ "pre-spc3-83" => DEFAULT_PAGE_CODE = PageCode::Page83PreSpc3, ++ _ => { ++ log::error!("unknown page code."); ++ return -1; ++ } ++ } ++ } ++ _ => { ++ println!("invalid arguments"); ++ r = -1; ++ break; ++ } ++ } ++ } ++ } ++ r ++} ++ ++fn do_scsi_page0_inquiry( ++ dev_scsi: &mut ScsiIdDevice, ++ fd: i32, ++ buffer: &mut [c_uchar], ++ len: u32, ++) -> bool { ++ let r = scsi_inquiry(dev_scsi, fd, 1, 0x0, buffer, len); ++ if r < 0 { ++ return false; ++ } ++ ++ if buffer[1] != 0_u8 { ++ log::debug!("{}: page 0 not available.", dev_scsi.kernel); ++ return false; ++ } ++ ++ if (buffer[3] as u32) > len { ++ log::debug!("{}: page 0 buffer too long {}", dev_scsi.kernel, buffer[3]); ++ return false; ++ } ++ ++ if (buffer[3] as i32) > (MODEL_LENGTH as i32) ++ && String::from_utf8(buffer[VENDOR_LENGTH..2 * VENDOR_LENGTH].to_vec()).unwrap() ++ != dev_scsi.vendor[VENDOR_LENGTH..] ++ { ++ log::debug!("{}: invalid page0 data", dev_scsi.kernel); ++ return false; ++ } ++ ++ true ++} ++ ++fn append_vendor_model(dev_scsi: &mut ScsiIdDevice) -> i32 { ++ if dev_scsi.vendor.len() >= VENDOR_LENGTH { ++ dev_scsi.serial += &dev_scsi.vendor[..VENDOR_LENGTH]; ++ } else { ++ log::debug!( ++ "append_vendor_model: {} bad vendor string {}", ++ dev_scsi.kernel, ++ dev_scsi.vendor ++ ); ++ return -EINVAL; ++ } ++ if dev_scsi.model.len() >= MODEL_LENGTH { ++ dev_scsi.serial += &dev_scsi.model[..MODEL_LENGTH]; ++ } else { ++ log::debug!( ++ "append_vendor_model: {} bad model string {}", ++ dev_scsi.kernel, ++ dev_scsi.model ++ ); ++ return -EINVAL; ++ } ++ (VENDOR_LENGTH + MODEL_LENGTH).try_into().unwrap() ++} ++ ++fn do_scsi_page80_inquiry( ++ dev_scsi: &mut ScsiIdDevice, ++ fd: i32, ++ get_serial: bool, ++ get_unit_serial_number: bool, ++ max_len: i32, ++) -> i32 { ++ let mut buffer = [0_u8; SCSI_INQ_BUFF_LEN as usize]; ++ let r = scsi_inquiry( ++ dev_scsi, ++ fd, ++ 1, ++ PageCode::Page80 as c_uchar, ++ &mut buffer, ++ SCSI_INQ_BUFF_LEN, ++ ); ++ if r < 0 { ++ return r; ++ } ++ ++ if buffer[1] != (PageCode::Page80 as c_uchar) { ++ log::debug!("{}: Invalid page 0x80.", dev_scsi.kernel); ++ return 1; ++ } ++ ++ let len = 1 + (VENDOR_LENGTH + MODEL_LENGTH) as i32 + buffer[3] as i32; ++ ++ if max_len < len { ++ log::debug!( ++ "{}: length {} too short - need {}.", ++ dev_scsi.kernel, ++ max_len, ++ len ++ ); ++ return 1; ++ } ++ ++ let len: usize = (buffer[3] + 4) as usize; ++ ++ if get_serial { ++ dev_scsi.serial = "S".to_string(); ++ if append_vendor_model(dev_scsi) < 0 { ++ return 1; ++ } ++ dev_scsi.serial += &String::from_utf8(buffer[4..len].to_vec()).unwrap(); ++ } ++ ++ if get_unit_serial_number { ++ dev_scsi.unit_serial_number = String::from_utf8(buffer[4..len].to_vec()).unwrap(); ++ } else { ++ dev_scsi.serial_short = String::from_utf8(buffer[4..len].to_vec()).unwrap(); ++ } ++ ++ 0 ++} ++ ++fn check_fill_0x83_prespc3(dev_scsi: &mut ScsiIdDevice, page_83: Vec, len: i32) -> i32 { ++ if dev_scsi.serial.is_empty() { ++ dev_scsi.serial = format!("{:x}", SCSI_ID_NAA); ++ } else { ++ dev_scsi ++ .serial ++ .replace_range(0..=0, &format!("{:x}", SCSI_ID_NAA)); ++ } ++ ++ for i in 0..page_83[3] as i32 { ++ dev_scsi.serial += &format!("{:x}", (page_83[4 + i as usize] as i32 & 0xf0) >> 4); ++ dev_scsi.serial += &format!("{:x}", page_83[4 + i as usize] as i32 & 0x0f); ++ if dev_scsi.serial.len() >= ((len - 3) as usize) { ++ break; ++ } ++ } ++ dev_scsi.serial_short = dev_scsi.serial.clone(); ++ 0 ++} ++ ++fn check_fill_0x83_id( ++ dev_scsi: &mut ScsiIdDevice, ++ page_83: Vec, ++ id_search: scsi_id_search_values, ++ max_len: i32, ++) -> i32 { ++ if (page_83[1] & 0x30) == 0x10 { ++ if id_search.id_type != SCSI_ID_TGTGROUP { ++ return 1; ++ } ++ } else if (page_83[1] & 0x30) != 0 { ++ return 1; ++ } ++ ++ if (page_83[1] & 0x0f) != id_search.id_type { ++ return 1; ++ } ++ ++ if id_search.naa_type != SCSI_ID_NAA_DONT_CARE && id_search.naa_type != (page_83[4] & 0xf0 >> 4) ++ { ++ return 1; ++ } ++ ++ if (page_83[0] & 0x0f) != id_search.code_set { ++ return 1; ++ } ++ ++ let mut len: i32 = page_83[3] as i32; ++ if (page_83[0] & 0x0f) != SCSI_ID_ASCII { ++ len *= 2; ++ } ++ ++ len += 2; ++ if id_search.id_type == SCSI_ID_VENDOR_SPECIFIC { ++ len += (VENDOR_LENGTH + MODEL_LENGTH) as i32; ++ } ++ ++ if max_len < len { ++ log::debug!( ++ "{}: length {} too short - need {}", ++ dev_scsi.kernel, ++ max_len, ++ len ++ ); ++ return 1; ++ } ++ ++ if id_search.id_type == SCSI_ID_TGTGROUP && !dev_scsi.tgpt_group.is_empty() { ++ let group = ((page_83[6] as c_uint) << 8) | page_83[7] as c_uint; ++ dev_scsi.tgpt_group = format!("{:x}", group); ++ return 1; ++ } ++ ++ if !dev_scsi.serial.is_empty() { ++ dev_scsi ++ .serial ++ .replace_range(0..=0, &format!("{:x}", id_search.id_type)); ++ } else { ++ dev_scsi.serial = format!("{:x}", id_search.id_type); ++ } ++ ++ if id_search.id_type == SCSI_ID_VENDOR_SPECIFIC && append_vendor_model(dev_scsi) < 0 { ++ return -1; ++ } ++ ++ let mut i = 4; /* offset to the start of the identifier */ ++ let s = dev_scsi.serial.len(); ++ if (page_83[0] as c_uchar & 0x0f) == SCSI_ID_ASCII { ++ let range = 4..(4 + page_83[3]); ++ dev_scsi.serial += ++ &String::from_utf8(page_83[range.start as usize..range.end as usize].to_vec()).unwrap(); ++ } else { ++ while i < (4 + page_83[3] as i32) { ++ dev_scsi.serial += &format!("{:x}", (page_83[i as usize] as usize & 0xf0) >> 4); ++ dev_scsi.serial += &format!("{:x}", page_83[i as usize] as usize & 0x0f); ++ i += 1; ++ } ++ } ++ ++ dev_scsi.serial_short = dev_scsi.serial[s..].to_string(); ++ ++ if id_search.id_type == SCSI_ID_NAA && dev_scsi.wwn.is_empty() { ++ let len = dev_scsi.serial.len(); ++ if len >= (s + 16) { ++ dev_scsi.wwn = dev_scsi.serial[s..s + 16].to_string(); ++ } else { ++ dev_scsi.wwn = dev_scsi.serial[s..].to_string(); ++ } ++ ++ if dev_scsi.wwn_vendor_extension.is_empty() { ++ if len >= (s + 32) { ++ dev_scsi.wwn_vendor_extension = dev_scsi.serial[s + 16..s + 32].to_string(); ++ } else if len >= (s + 16) { ++ dev_scsi.wwn_vendor_extension = dev_scsi.serial[s + 16..].to_string(); ++ } ++ } ++ } ++ ++ 0 ++} ++ ++fn do_scsi_page83_inquiry(dev_scsi: &mut ScsiIdDevice, fd: i32, len: i32) -> i32 { ++ let mut page_83 = vec![0_u8; SCSI_INQ_BUFF_LEN as usize]; ++ do_scsi_page80_inquiry(dev_scsi, fd, false, true, MAX_SERIAL_LEN); ++ let mut r = scsi_inquiry( ++ dev_scsi, ++ fd, ++ 1, ++ PageCode::Page83 as u8, ++ &mut page_83, ++ SCSI_INQ_BUFF_LEN, ++ ); ++ if r < 0 { ++ return 1; ++ } ++ ++ if page_83[1] != (PageCode::Page83 as c_uchar) { ++ log::debug!("{}: Invalid page 0x83.", dev_scsi.kernel); ++ return 1; ++ } ++ ++ if page_83[6] != 0_u8 { ++ return check_fill_0x83_prespc3(dev_scsi, page_83, len); ++ } ++ ++ for id in &ID_SEARCH_LIST { ++ let mut i = 4; ++ while i <= (((page_83[2] as c_uint) << 8) + page_83[3] as c_uint + 3) { ++ r = check_fill_0x83_id( ++ dev_scsi, ++ page_83.clone().split_off(i.try_into().unwrap()), ++ *id, ++ len, ++ ); ++ if r <= 0 { ++ return r; ++ } ++ i += page_83[i as usize + 3] as c_uint + 4; ++ } ++ } ++ ++ 1 ++} ++ ++fn do_scsi_page83_prespc3_inquiry(dev_scsi: &mut ScsiIdDevice, fd: i32) -> i32 { ++ let mut page_83 = vec![0_u8; SCSI_INQ_BUFF_LEN as usize]; ++ let r = scsi_inquiry( ++ dev_scsi, ++ fd, ++ 1, ++ PageCode::Page83 as u8, ++ &mut page_83, ++ SCSI_INQ_BUFF_LEN, ++ ); ++ if r < 0 { ++ return 1; ++ } ++ ++ if page_83[1] != (PageCode::Page83 as c_uchar) { ++ log::debug!("{}: Invalid page 0x83.", dev_scsi.kernel); ++ return 1; ++ } ++ ++ if page_83[6] == 0_u8 { ++ return 2; ++ } ++ ++ if dev_scsi.serial.is_empty() { ++ dev_scsi.serial += &format!("{:x}", SCSI_ID_NAA); ++ } else { ++ dev_scsi ++ .serial ++ .replace_range(0..1, &format!("{:x}", SCSI_ID_NAA)); ++ } ++ ++ let mut i = 4; ++ ++ while i < (page_83[3] as i32 + 4) { ++ dev_scsi.serial += &format!("{:x}", (page_83[i as usize] as usize & 0xf0) >> 4); ++ dev_scsi.serial += &format!("{:x}", page_83[i as usize] as usize & 0x0f); ++ i += 1; ++ } ++ ++ 0 ++} ++ ++fn scsi_get_serial( ++ dev_scsi: &mut ScsiIdDevice, ++ devname: &str, ++ page_code: &PageCode, ++ len: i32, ++) -> i32 { ++ dev_scsi.serial = String::new(); ++ let mut fd = -EBADF; ++ let mut page0 = vec![0_u8; SCSI_INQ_BUFF_LEN as usize]; ++ for _i in 0..=20 { ++ fd = match open( ++ devname, ++ OFlag::O_RDONLY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC | OFlag::O_NOCTTY, ++ Mode::from_bits(0o666).unwrap(), ++ ) { ++ Ok(fd) => fd, ++ Err(e) => { ++ log::error!("Cannot open {}: {}", devname, e); ++ if e != Errno::EBUSY { ++ break; ++ } ++ -1 ++ } ++ }; ++ ++ if fd >= 0 { ++ break; ++ } ++ thread::sleep(Duration::from_millis(200)); ++ } ++ ++ if fd < 0 { ++ return 1; ++ } ++ ++ match page_code { ++ PageCode::Page80 => { ++ if do_scsi_page80_inquiry(dev_scsi, fd, true, false, len) != 0 { ++ return 1; ++ } else { ++ return 0; ++ } ++ } ++ PageCode::Page83 => { ++ if do_scsi_page83_inquiry(dev_scsi, fd, len) != 0 { ++ return 1; ++ } else { ++ return 0; ++ } ++ } ++ PageCode::Page83PreSpc3 => { ++ let r = do_scsi_page83_prespc3_inquiry(dev_scsi, fd); ++ if r != 0 { ++ if r == 2 { ++ if do_scsi_page83_inquiry(dev_scsi, fd, len) != 0 { ++ return 1; ++ } else { ++ return 0; ++ } ++ } else { ++ return 1; ++ } ++ } else { ++ return r; ++ } ++ } ++ PageCode::PageUnspecified => {} ++ } ++ ++ if !do_scsi_page0_inquiry(dev_scsi, fd, &mut page0, SCSI_INQ_BUFF_LEN) { ++ return 1; ++ } ++ ++ for ind in 4..=(page0[3] as i32 + 3) { ++ if page0[ind as usize] == (PageCode::Page83 as c_uchar) ++ && do_scsi_page83_inquiry(dev_scsi, fd, len) == 0 ++ { ++ return 0; ++ } ++ } ++ ++ for ind in 4..=(page0[3] as i32 + 3) { ++ if page0[ind as usize] == (PageCode::Page80 as c_uchar) ++ && do_scsi_page80_inquiry(dev_scsi, fd, true, false, len) == 0 ++ { ++ return 0; ++ } ++ } ++ 1 ++} ++ ++fn scsi_id(maj_min_dev: &str) -> i32 { ++ let mut dev_scsi = ScsiIdDevice::default(); ++ let page_code = unsafe { &DEFAULT_PAGE_CODE }; ++ ++ if set_inq_values(&mut dev_scsi, maj_min_dev) < 0 { ++ return 1; ++ } ++ ++ per_dev_options(&mut dev_scsi); ++ if !unsafe { ALL_GOOD } { ++ return 1; ++ } ++ ++ scsi_get_serial(&mut dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); ++ unsafe { ++ if EXPORT { ++ println!("ID_SCSI=1"); ++ println!("ID_VENDOR={}", &VENDOR); ++ println!("ID_VENDOR_ENC={}", &VENDOR_ENC_STR); ++ println!("ID_MODEL={}", &MODEL); ++ println!("ID_MODEL_ENC={}", &MODEL_ENC_STR); ++ println!("ID_REVISION={}", &REVISION); ++ println!("ID_TYPE={}", &TYPE_STR); ++ ++ if !dev_scsi.serial.is_empty() { ++ dev_scsi.serial = replace_whitespace(&dev_scsi.serial); ++ dev_scsi.serial = replace_chars(&dev_scsi.serial, ""); ++ println!("ID_SERIAL={}", dev_scsi.serial); ++ dev_scsi.serial_short = replace_whitespace(&dev_scsi.serial_short); ++ dev_scsi.serial_short = replace_chars(&dev_scsi.serial_short, ""); ++ println!("ID_SERIAL_SHORT={}", dev_scsi.serial_short); ++ } ++ ++ if !dev_scsi.wwn.is_empty() { ++ println!("ID_WWN=0x{}", dev_scsi.wwn); ++ if !dev_scsi.wwn_vendor_extension.is_empty() { ++ println!( ++ "ID_WWN_VENDOR_EXTENSION=0x{}", ++ dev_scsi.wwn_vendor_extension ++ ); ++ println!( ++ "ID_WWN_WITH_EXTENSION=0x{}{}", ++ dev_scsi.wwn, dev_scsi.wwn_vendor_extension ++ ); ++ } else { ++ println!("ID_WWN_WITH_EXTENSION=0x{}", dev_scsi.wwn); ++ } ++ } ++ ++ if !dev_scsi.tgpt_group.is_empty() { ++ println!("ID_TARGET_PORT={}", dev_scsi.tgpt_group); ++ } ++ ++ if !dev_scsi.unit_serial_number.is_empty() { ++ println!("ID_SCSI_SERIAL={}", dev_scsi.unit_serial_number); ++ } ++ ++ return 0; ++ } ++ } ++ ++ if dev_scsi.serial.is_empty() { ++ return 1; ++ } ++ ++ if unsafe { REFORMAT_SERIAL } { ++ dev_scsi.serial = replace_whitespace(&dev_scsi.serial); ++ dev_scsi.serial = replace_chars(&dev_scsi.serial, ""); ++ println!("{}", dev_scsi.serial); ++ return 0; ++ } ++ ++ println!("{}", dev_scsi.serial); ++ 0 ++} ++ ++fn main() { ++ let args: Vec = std::env::args().collect(); ++ let argc = args.len() as i32; ++ ++ let r = get_file_options(String::from(""), String::from("")); ++ if r < 0 { ++ exit(1); ++ } ++ ++ if r == 0 && set_options(unsafe { ARGC }, unsafe { ARGV.clone() }) < 0 { ++ exit(2); ++ } ++ ++ if set_options(argc, args) < 0 { ++ exit(1); ++ } ++ ++ if !unsafe { DEV_SPECIFIED } { ++ log::error!("No device specified!"); ++ exit(1); ++ } ++ ++ let r = scsi_id(unsafe { &MAJ_MIN_DEV }); ++ exit(r); ++} +-- +2.33.0 + diff --git a/backport-feature-devctl-add-devctl-info.patch b/backport-feature-devctl-add-devctl-info.patch new file mode 100644 index 0000000..db2221c --- /dev/null +++ b/backport-feature-devctl-add-devctl-info.patch @@ -0,0 +1,1093 @@ +From abc6eb6912fa8113ebe6238d95edbe920c703a30 Mon Sep 17 00:00:00 2001 +From: huyubiao +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 = std::result::Result; ++ + /// 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, ++ ++ /// 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)] +@@ -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 = 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 = 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, 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 = 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 = std::result::Result; ++ ++/// find device by path or unit name ++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); ++ } ++ } ++ ++ /* 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 { ++ 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>, + 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 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 { + 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 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::>(); +- 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::>(); +- 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", ¤t_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 + diff --git a/backport-feature-devmaster-Add-subcommands-for-devctl-trigger.patch b/backport-feature-devmaster-Add-subcommands-for-devctl-trigger.patch new file mode 100644 index 0000000..be0fd08 --- /dev/null +++ b/backport-feature-devmaster-Add-subcommands-for-devctl-trigger.patch @@ -0,0 +1,1343 @@ +From ac13301c681cd4a4b50e4874b8f6c1f7b81e9f13 Mon Sep 17 00:00:00 2001 +From: huyubiao +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 = std::result::Result; ++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, + +- /// 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, + +- /// 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>, ++ ++ /// Exclude devices from a matching subsystem ++ #[clap(short('S'), long)] ++ subsystem_nomatch: Option>, ++ ++ /// Trigger devices with a matching attribute ++ #[clap(short('a'), long)] ++ attr_match: Option>, ++ ++ /// Exclude devices with a matching attribute ++ #[clap(short('A'), long)] ++ attr_nomatch: Option>, ++ ++ /// Trigger devices with a matching property ++ #[clap(short('p'), long)] ++ property_match: Option>, ++ ++ /// Trigger devices with a matching tag ++ #[clap(short('g'), long)] ++ tag_match: Option>, ++ ++ /// Trigger devices with this /sys path ++ #[clap(short('y'), long)] ++ sysname_match: Option>, ++ ++ /// Trigger devices with this /dev name ++ #[clap(long)] ++ name_match: Option>, ++ ++ /// Trigger devices with this /sys path ++ #[clap(short('b'), long)] ++ parent_match: Option>, ++ ++ /// 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, +- +- /// 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 = std::result::Result; +- + #[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, ++#[derive(Debug)] ++pub struct TriggerArgs { ++ action: Option, + r#type: Option, + verbose: bool, +- action: Option, + dry_run: bool, +-) { +- let mut devlist = vec![]; ++ subsystem_match: Option>, ++ subsystem_nomatch: Option>, ++ attr_match: Option>, ++ attr_nomatch: Option>, ++ property_match: Option>, ++ tag_match: Option>, ++ sysname_match: Option>, ++ name_match: Option>, ++ parent_match: Option>, ++ settle: bool, ++ uuid: bool, ++ devices: Vec, ++} ++ ++impl TriggerArgs { ++ #[allow(clippy::too_many_arguments)] ++ pub fn new( ++ action: Option, ++ r#type: Option, ++ verbose: bool, ++ dry_run: bool, ++ subsystem_match: Option>, ++ subsystem_nomatch: Option>, ++ attr_match: Option>, ++ attr_nomatch: Option>, ++ property_match: Option>, ++ tag_match: Option>, ++ sysname_match: Option>, ++ name_match: Option>, ++ parent_match: Option>, ++ settle: bool, ++ uuid: bool, ++ devices: Vec, ++ ) -> 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::().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::().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> { ++ let mut uuid_supported = -1; ++ let mut uuids = HashSet::new(); ++ let mut ret = Ok(HashSet::::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>, ++ 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, ++ 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 = std::result::Result; + +@@ -11,14 +12,22 @@ pub fn find_device(id: &str, prefix: &str) -> Result { + 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 = std::result::Result; +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 { +- 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) -> Result { +@@ -58,3 +60,53 @@ pub fn wait_for_events(fd: RawFd, event: PollFlags, time_out: i64) -> Result Result { ++ 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 { +@@ -167,6 +167,33 @@ impl Uuid { + } + } + ++/// get random uuid ++pub fn randomize() -> Result { ++ 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, 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, 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>>, ++ pub(crate) match_sysattr: RefCell>, + /// do not match sysattr + /// key: sysattr, value: match value +- pub(crate) not_match_sysattr: RefCell>>, ++ pub(crate) not_match_sysattr: RefCell>, + + /// 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, ++ device: &Device, ++ sysattr: &str, ++ patterns: &str, + ) -> Result { +- 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 { +- 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 + diff --git a/backport-feature-devmaster-add-devctl-control-subcommand-to-l.patch b/backport-feature-devmaster-add-devctl-control-subcommand-to-l.patch new file mode 100644 index 0000000..721e8c3 --- /dev/null +++ b/backport-feature-devmaster-add-devctl-control-subcommand-to-l.patch @@ -0,0 +1,199 @@ +From 714eeceecbe09fd8855cd84279dc13e45a093fb2 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 16 Nov 2023 08:07:07 +0800 +Subject: [PATCH 064/103] feature(devmaster): add devctl control subcommand to + let devmaster exit + +Also decouple the configuration path into the parameter of run_daemon +function for unit test. +--- + exts/devmaster/src/bin/devctl/daemon/mod.rs | 37 ++++++++++++++++++- + exts/devmaster/src/bin/devctl/main.rs | 19 +++++++++- + .../src/lib/framework/control_manager.rs | 9 ++++- + exts/devmaster/src/lib/framework/devmaster.rs | 5 ++- + 4 files changed, 63 insertions(+), 7 deletions(-) + +diff --git a/exts/devmaster/src/bin/devctl/daemon/mod.rs b/exts/devmaster/src/bin/devctl/daemon/mod.rs +index 6dfe9718..3e4bd96d 100644 +--- a/exts/devmaster/src/bin/devctl/daemon/mod.rs ++++ b/exts/devmaster/src/bin/devctl/daemon/mod.rs +@@ -38,10 +38,10 @@ fn notify(unset_env: bool, msg: String) -> std::io::Result<()> { + } + } + +-pub fn run_daemon() { ++pub fn run_daemon(config_path: &str) { + let events = Rc::new(Events::new().unwrap()); + +- let devmaster = Devmaster::new(events); ++ let devmaster = Devmaster::new(config_path, events); + + if let Err(e) = notify(false, "READY=1\n".to_string()) { + log::warn!("Failed to notify pid 1: {}", e); +@@ -51,3 +51,36 @@ pub fn run_daemon() { + + devmaster.as_ref().borrow().exit(); + } ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ use device::{Device, DeviceAction}; ++ use libdevmaster::framework::control_manager::CONTROL_MANAGER_LISTEN_ADDR; ++ use std::io::Write; ++ ++ #[test] ++ fn test_run_daemon() { ++ /* Require root privilege, skip in ci environment. */ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ if dev.trigger(DeviceAction::Change).is_err() { ++ return; ++ } ++ ++ std::thread::spawn(|| { ++ std::thread::sleep(std::time::Duration::from_secs(1)); ++ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ dev.trigger(DeviceAction::Change).unwrap(); ++ ++ /* Sleep more than 3 seconds to wait for the workers being recycled. */ ++ std::thread::sleep(std::time::Duration::from_secs(4)); ++ ++ let mut stream = ++ std::os::unix::net::UnixStream::connect(CONTROL_MANAGER_LISTEN_ADDR).unwrap(); ++ stream.write_all(b"exit ").unwrap(); ++ }); ++ ++ run_daemon("none"); ++ } ++} +diff --git a/exts/devmaster/src/bin/devctl/main.rs b/exts/devmaster/src/bin/devctl/main.rs +index 17645ee6..8f72285e 100644 +--- a/exts/devmaster/src/bin/devctl/main.rs ++++ b/exts/devmaster/src/bin/devctl/main.rs +@@ -18,6 +18,7 @@ mod subcmds; + use basic::argv_util::invoked_as; + use clap::Parser; + use daemon::run_daemon; ++use libdevmaster::config::devmaster_conf::DEFAULT_CONFIG; + use libdevmaster::framework::control_manager::CONTROL_MANAGER_LISTEN_ADDR; + use log::init_log_to_console_syslog; + use log::Level; +@@ -204,6 +205,12 @@ enum SubCmd { + #[clap(short, long)] + root: Option, + }, ++ /// ++ #[clap(display_order = 7)] ++ Control { ++ #[clap(short, long)] ++ exit: bool, ++ }, + } + + /// subcommand for killing workers +@@ -212,10 +219,19 @@ fn subcommand_kill() { + stream.write_all(b"kill ").unwrap(); + } + ++/// subcommand for controlling devmaster ++fn subcommand_control(exit: bool) { ++ let mut stream = UnixStream::connect(CONTROL_MANAGER_LISTEN_ADDR).unwrap(); ++ ++ if exit { ++ stream.write_all(b"exit ").unwrap(); ++ } ++} ++ + fn main() -> Result<()> { + let argv: Vec = std::env::args().collect(); + if invoked_as(argv, "devmaster") { +- run_daemon(); ++ run_daemon(DEFAULT_CONFIG); + return Ok(()); + } + +@@ -300,6 +316,7 @@ fn main() -> Result<()> { + strict, + root, + } => subcommand_hwdb(update, test, path, usr, strict, root), ++ SubCmd::Control { exit } => subcommand_control(exit), + } + + Ok(()) +diff --git a/exts/devmaster/src/lib/framework/control_manager.rs b/exts/devmaster/src/lib/framework/control_manager.rs +index 342c6a0c..d5a5f440 100644 +--- a/exts/devmaster/src/lib/framework/control_manager.rs ++++ b/exts/devmaster/src/lib/framework/control_manager.rs +@@ -14,7 +14,7 @@ + //! + use crate::framework::job_queue::JobQueue; + use crate::framework::worker_manager::WorkerManager; +-use event::Source; ++use event::{Events, Source}; + use nix::unistd::unlink; + use std::os::unix::net::UnixListener; + use std::path::Path; +@@ -38,7 +38,7 @@ pub struct ControlManager { + worker_manager: Weak, + /// reference to job queue + _job_queue: Weak, +- // events: Rc, ++ events: Rc, + } + + /// public methods +@@ -48,6 +48,7 @@ impl ControlManager { + listen_addr: String, + worker_manager: Rc, + job_queue: Rc, ++ events: Rc, + ) -> ControlManager { + /* + * Cleanup remaining socket if it exists. +@@ -67,6 +68,7 @@ impl ControlManager { + listener, + worker_manager: Rc::downgrade(&worker_manager), + _job_queue: Rc::downgrade(&job_queue), ++ events, + } + } + } +@@ -83,6 +85,9 @@ impl ControlManager { + "kill" => { + self.worker_manager.upgrade().unwrap().kill_workers(); + } ++ "exit" => { ++ self.events.set_exit(); ++ } + _ => { + todo!(); + } +diff --git a/exts/devmaster/src/lib/framework/devmaster.rs b/exts/devmaster/src/lib/framework/devmaster.rs +index 94b93ca0..20735614 100644 +--- a/exts/devmaster/src/lib/framework/devmaster.rs ++++ b/exts/devmaster/src/lib/framework/devmaster.rs +@@ -71,10 +71,10 @@ impl Cache { + + impl Devmaster { + /// generate a devmaster object +- pub fn new(events: Rc) -> Rc> { ++ pub fn new(config_path: &str, events: Rc) -> Rc> { + let config = DevmasterConfig::new(); + +- config.load(DEFAULT_CONFIG); ++ config.load(config_path); + + init_log( + "devmaster", +@@ -127,6 +127,7 @@ impl Devmaster { + String::from(CONTROL_MANAGER_LISTEN_ADDR), + worker_manager.clone(), + job_queue.clone(), ++ events.clone(), + )); + let monitor = Rc::new(UeventMonitor::new(job_queue.clone())); + let post = Rc::new(GarbageCollect::new(&devmaster)); +-- +2.33.0 + diff --git a/backport-fix-Failed-to-devctl-info-dev-or-sys.patch b/backport-fix-Failed-to-devctl-info-dev-or-sys.patch new file mode 100644 index 0000000..1b9d0e1 --- /dev/null +++ b/backport-fix-Failed-to-devctl-info-dev-or-sys.patch @@ -0,0 +1,157 @@ +From b5b35912af30bd37d38496af6a28aec5cd27159e Mon Sep 17 00:00:00 2001 +From: huyubiao +Date: Wed, 8 Nov 2023 06:53:14 +0800 +Subject: [PATCH 038/103] fix: Failed to devctl info /dev or /sys + +--- + .../src/bin/devctl/subcmds/devctl_info.rs | 67 ++++++------------- + 1 file changed, 21 insertions(+), 46 deletions(-) + +diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs +index e18c3898..71c9254d 100644 +--- a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs ++++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs +@@ -35,20 +35,6 @@ enum QueryType { + 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, +@@ -96,11 +82,6 @@ impl InfoArgs { + pub fn subcommand(&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(); + } +@@ -113,12 +94,9 @@ impl InfoArgs { + 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); ++ 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"); +@@ -145,43 +123,44 @@ impl InfoArgs { + }; + + 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); ++ r = self.query_device(device); + } else if self.attribute_walk { + r = print_device_chain(device); + } else { +- log::error!("unknown action"); +- return Err(nix::Error::EINVAL); ++ r = self.query_device(device); + } + } + + r + } + +- fn parse_query_type(&self, query_type: &mut QueryType) -> Result<()> { ++ fn is_export(&self) -> bool { ++ self.export || self.export_prefix.is_some() ++ } ++ ++ fn parse_query_type(&self) -> Result { + match &self.query { + Some(q) => { + if q == "property" || q == "env" { +- *query_type = QueryType::Property; ++ Ok(QueryType::Property) + } else if q == "name" { +- *query_type = QueryType::Name; ++ Ok(QueryType::Name) + } else if q == "symlink" { +- *query_type = QueryType::Symlink; ++ Ok(QueryType::Symlink) + } else if q == "path" { +- *query_type = QueryType::Path; ++ Ok(QueryType::Path) + } else if q == "all" { +- *query_type = QueryType::All; ++ Ok(QueryType::All) + } else { + log::error!("unknown query type"); +- return Err(nix::Error::EINVAL); ++ Err(nix::Error::EINVAL) + } + } +- None => *query_type = QueryType::All, ++ None => Ok(QueryType::All), + } +- Ok(()) + } + +- fn stat_device(&self, name: &str, export: bool) -> Result<()> { ++ fn stat_device(&self, name: &str) -> Result<()> { + let metadata = match fs::metadata(name) { + Ok(metadata) => metadata, + Err(err) => { +@@ -190,7 +169,7 @@ impl InfoArgs { + } + }; + +- if export { ++ if self.is_export() { + match &self.export_prefix { + Some(p) => { + println!("{}MAJOR={}", p, nix::sys::stat::major(metadata.dev())); +@@ -212,12 +191,8 @@ impl InfoArgs { + Ok(()) + } + +- fn query_device( +- &self, +- query: &QueryType, +- device: Device, +- property: QueryProperty, +- ) -> Result<()> { ++ fn query_device(&self, device: Device) -> Result<()> { ++ let query = self.parse_query_type()?; + match query { + QueryType::Name => { + let node = match device.get_devname() { +@@ -275,8 +250,8 @@ impl InfoArgs { + } + QueryType::Property => { + for (key, value) in &device.property_iter() { +- if property.export { +- match &property.export_prefix { ++ if self.is_export() { ++ match &self.export_prefix { + Some(export_prefix) => println!("{}{}='{}'", export_prefix, key, value), + None => println!("{}='{}'", key, value), + } +-- +2.33.0 + diff --git a/backport-fix-basic-complete-feature-dependencies.patch b/backport-fix-basic-complete-feature-dependencies.patch new file mode 100644 index 0000000..bc7088f --- /dev/null +++ b/backport-fix-basic-complete-feature-dependencies.patch @@ -0,0 +1,81 @@ +From 241471ed8d54b769831fc383075233df967a897a Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 02:09:09 +0800 +Subject: [PATCH 045/103] fix(basic): complete feature dependencies + +uuid depends on random. + +random depends on io. + +Also add get_errno method for basic Error. +--- + libs/basic/Cargo.toml | 4 ++-- + libs/basic/src/error.rs | 36 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+), 2 deletions(-) + +diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml +index f7c84157..654c1a3b 100644 +--- a/libs/basic/Cargo.toml ++++ b/libs/basic/Cargo.toml +@@ -111,9 +111,9 @@ string = [] + sysfs = ["nix/dir"] + unistd = ["nix/user"] + unit_name = [] +-uuid = ["bitflags"] ++uuid = ["bitflags", "random"] + murmurhash2 = [] + strbuf = [] + argv = [] + exec_util = [] +-random = [] ++random = ["io"] +diff --git a/libs/basic/src/error.rs b/libs/basic/src/error.rs +index aead8ce7..d138f477 100644 +--- a/libs/basic/src/error.rs ++++ b/libs/basic/src/error.rs +@@ -70,6 +70,42 @@ pub enum Error { + Other { msg: String }, + } + ++impl Error { ++ /// Translate the basic error to error number. ++ pub fn get_errno(&self) -> i32 { ++ match self { ++ Self::Syscall { ++ syscall: _, ++ ret: _, ++ errno, ++ } => *errno, ++ Error::Io { source } => source.raw_os_error().unwrap_or_default(), ++ Error::Caps { what: _ } => nix::errno::Errno::EINVAL as i32, ++ Error::Nix { source } => *source as i32, ++ Error::Var { source } => { ++ (match source { ++ std::env::VarError::NotPresent => nix::errno::Errno::ENOENT, ++ std::env::VarError::NotUnicode(_) => nix::errno::Errno::EINVAL, ++ }) as i32 ++ } ++ Error::Proc { source } => match source { ++ procfs::ProcError::Incomplete(_) => nix::errno::Errno::EINVAL as i32, ++ procfs::ProcError::PermissionDenied(_) => nix::errno::Errno::EPERM as i32, ++ procfs::ProcError::NotFound(_) => nix::errno::Errno::ENOENT as i32, ++ procfs::ProcError::Io(_, _) => nix::errno::Errno::EIO as i32, ++ procfs::ProcError::Other(_) => nix::errno::Errno::EINVAL as i32, ++ procfs::ProcError::InternalError(_) => nix::errno::Errno::EINVAL as i32, ++ }, ++ Error::NulError { source: _ } => nix::errno::Errno::EINVAL as i32, ++ Error::Parse { source: _ } => nix::errno::Errno::EINVAL as i32, ++ Error::ParseNamingScheme { what: _ } => nix::errno::Errno::EINVAL as i32, ++ Error::NotExisted { what: _ } => nix::errno::Errno::ENOENT as i32, ++ Error::Invalid { what: _ } => nix::errno::Errno::EINVAL as i32, ++ Error::Other { msg: _ } => nix::errno::Errno::EINVAL as i32, ++ } ++ } ++} ++ + #[allow(unused_macros)] + macro_rules! errfrom { + ($($st:ty),* => $variant:ident) => ( +-- +2.33.0 + diff --git a/backport-fix-basic-use-feature-to-control-procfs-compilation-.patch b/backport-fix-basic-use-feature-to-control-procfs-compilation-.patch new file mode 100644 index 0000000..5cbddb2 --- /dev/null +++ b/backport-fix-basic-use-feature-to-control-procfs-compilation-.patch @@ -0,0 +1,25 @@ +From 9af6528c348feab7c463a0710f6dacc8ac57db95 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 05:17:04 +0800 +Subject: [PATCH 048/103] fix(basic): use feature to control procfs compilation + in get_errno + +--- + libs/basic/src/error.rs | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libs/basic/src/error.rs b/libs/basic/src/error.rs +index d138f477..89ac5a3e 100644 +--- a/libs/basic/src/error.rs ++++ b/libs/basic/src/error.rs +@@ -88,6 +88,7 @@ impl Error { + std::env::VarError::NotUnicode(_) => nix::errno::Errno::EINVAL, + }) as i32 + } ++ #[cfg(feature = "process")] + Error::Proc { source } => match source { + procfs::ProcError::Incomplete(_) => nix::errno::Errno::EINVAL as i32, + procfs::ProcError::PermissionDenied(_) => nix::errno::Errno::EPERM as i32, +-- +2.33.0 + diff --git a/backport-fix-device-avoid-panic-on-shallow-clone-when-subsyst.patch b/backport-fix-device-avoid-panic-on-shallow-clone-when-subsyst.patch new file mode 100644 index 0000000..7c176c7 --- /dev/null +++ b/backport-fix-device-avoid-panic-on-shallow-clone-when-subsyst.patch @@ -0,0 +1,86 @@ +From a71d702d98d090eb0bc68fa7950a590967de8716 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 23 Nov 2023 14:23:46 +0800 +Subject: [PATCH 082/103] fix(device): avoid panic on shallow clone when + subsystem is not available + +Some special sysfs device, such as /sys/devices/platform, may not contain +available subsystem. This leads to panic in shallow cloning such device +object when getting its subsystem. +--- + libs/device/src/device.rs | 51 ++++++++++++++++++++++++--------------- + 1 files changed, 32 insertions(+), 19 deletions(-) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index e8e2dcc5..a9cb196c 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -2396,14 +2396,15 @@ impl Device { + + device.set_syspath(&syspath, false)?; + +- let subsystem = self.get_subsystem()?; ++ /* Some devices, such as /sys/devices/platform, do not have subsystem. */ ++ if let Ok(subsystem) = self.get_subsystem() { ++ device.set_subsystem(&subsystem); + +- device.set_subsystem(&subsystem); +- +- if subsystem == "drivers" { +- device +- .driver_subsystem +- .replace(self.driver_subsystem.borrow().clone()); ++ if subsystem == "drivers" { ++ device ++ .driver_subsystem ++ .replace(self.driver_subsystem.borrow().clone()); ++ } + } + + if let Ok(ifindex) = self.get_property_value("IFINDEX") { +@@ -3506,19 +3507,31 @@ mod tests { + + #[test] + fn test_shallow_clone() { +- #[inline] +- fn inner_test(dev: &mut Device) -> Result<(), Error> { +- let s1 = dev.get_syspath().unwrap(); +- +- let dev_clone = dev.shallow_clone().unwrap(); +- +- assert_eq!(s1, dev_clone.get_syspath().unwrap()); +- +- Ok(()) +- } ++ /* Enumerator merely collect devices with valid subsystems, ++ * while get_parent method may not, e.g., /sys/devices/platform. ++ */ ++ let mut e = DeviceEnumerator::new(); ++ e.set_enumerator_type(DeviceEnumerationType::All); ++ ++ for mut dev in e.iter() { ++ let dev_clone = dev.borrow().shallow_clone().unwrap(); ++ dev_clone.get_syspath().unwrap(); ++ dev_clone.get_subsystem().unwrap(); ++ ++ loop { ++ let ret = dev.borrow().get_parent(); ++ ++ if let Ok(parent) = ret { ++ parent.borrow().get_syspath().unwrap(); ++ if let Err(e) = parent.borrow().get_subsystem() { ++ assert_eq!(e.get_errno(), Errno::ENOENT); ++ } ++ dev = parent; ++ continue; ++ } + +- if let Err(e) = LoopDev::inner_process("/tmp/test_shallow_clone", 1024 * 10, inner_test) { +- assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ break; ++ } + } + } + +2.33.0 + diff --git a/backport-fix-device-cleanup-temporary-tag-files.patch b/backport-fix-device-cleanup-temporary-tag-files.patch new file mode 100644 index 0000000..5e3aea1 --- /dev/null +++ b/backport-fix-device-cleanup-temporary-tag-files.patch @@ -0,0 +1,35 @@ +From 23a1923d6ef767e1f49a18129e515a67bf4ff70c Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 14 Nov 2023 22:35:01 +0800 +Subject: [PATCH 060/103] fix(device): cleanup temporary tag files + +Also make a tiny change to optimize the doc conmments. +--- + libs/device/src/device.rs | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 6a43be82..70070c72 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -2774,7 +2774,7 @@ impl Device { + } + + /// Return the devlink iterator +- /// ++ /// + /// The device object will try to load devlinks from db firstly. + pub fn devlink_iter(&self) -> HashSetRefWrapper { + let _ = self.read_db(); +@@ -3452,6 +3452,8 @@ mod tests { + + dev.cleanup_tags(); + ++ fs::remove_dir_all("/tmp/devmaster").unwrap(); ++ + Ok(()) + } + +-- +2.33.0 + diff --git a/backport-fix-device-drop-unnecessary-error-throwing-out.patch b/backport-fix-device-drop-unnecessary-error-throwing-out.patch new file mode 100644 index 0000000..75db7ef --- /dev/null +++ b/backport-fix-device-drop-unnecessary-error-throwing-out.patch @@ -0,0 +1,96 @@ +From ed46964df74ff592f39295fe20d083b144089310 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 14 Nov 2023 17:14:36 +0800 +Subject: [PATCH 059/103] fix(device): drop unnecessary error throwing out + +In some device methods, the device object should prepare inner data by reading +database, but it tolerates failure. +--- + libs/device/src/device.rs | 43 +++++++++++++-------------------------- + 1 file changed, 14 insertions(+), 29 deletions(-) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 3845e21c..6a43be82 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -955,14 +955,14 @@ impl Device { + + /// check whether the device has the tag + pub fn has_tag(&self, tag: &str) -> Result { +- self.read_db()?; ++ let _ = self.read_db(); + + Ok(self.all_tags.borrow().contains(tag)) + } + + /// check whether the device has the current tag + pub fn has_current_tag(&self, tag: &str) -> Result { +- self.read_db()?; ++ let _ = self.read_db(); + + Ok(self.current_tags.borrow().contains(tag)) + } +@@ -2751,48 +2751,33 @@ impl<'a, 'b: 'a, K: 'a, V: 'a> IntoIterator for &'b HashMapRefWrapper<'a, K, V> + } + + impl Device { +- /// return the tag iterator ++ /// Return the tag iterator. ++ /// ++ /// The device object will try to load tags from db firstly. + pub fn tag_iter(&self) -> HashSetRefWrapper { +- if let Err(e) = self.read_db() { +- log::debug!( +- "failed to read db of '{}': {}", +- self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), +- e +- ) +- } ++ let _ = self.read_db(); + + HashSetRefWrapper { + r: self.all_tags.borrow(), + } + } + +- /// return the current tag iterator ++ /// Return the current tag iterator. ++ /// ++ /// The device object will try to load tags from db firstly. + pub fn current_tag_iter(&self) -> HashSetRefWrapper { +- if let Err(e) = self.read_db() { +- log::error!( +- "failed to read db of '{}': {}", +- self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), +- e +- ) +- } ++ let _ = self.read_db(); + + HashSetRefWrapper { + r: self.current_tags.borrow(), + } + } + +- /// return the tag iterator ++ /// Return the devlink iterator ++ /// ++ /// The device object will try to load devlinks from db firstly. + pub fn devlink_iter(&self) -> HashSetRefWrapper { +- if let Err(e) = self.read_db() { +- log::debug!( +- "failed to read db of '{}': {}", +- self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), +- e +- ) +- } ++ let _ = self.read_db(); + + HashSetRefWrapper { + r: self.devlinks.borrow(), +-- +2.33.0 + diff --git a/backport-fix-device-fix-UT-privilege-error-in-ci.patch b/backport-fix-device-fix-UT-privilege-error-in-ci.patch new file mode 100644 index 0000000..d4c9e7a --- /dev/null +++ b/backport-fix-device-fix-UT-privilege-error-in-ci.patch @@ -0,0 +1,75 @@ +From f5299cda3a36ea68d248835db6733bbd14fd25cc Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 16:55:29 +0800 +Subject: [PATCH 051/103] fix(device): fix UT privilege error in ci + +The user in ci has limited privileges, which will prevent actions +like writing some device sysattrs. +--- + libs/device/src/device.rs | 23 ++++++++++++++--------- + 1 file changed, 14 insertions(+), 9 deletions(-) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 284cd6e9..4f5779ce 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -3626,7 +3626,7 @@ G:devmaster + Q:devmaster + V:100 + "; +- touch_file("/tmp/tmp_db", false, None, None, None).unwrap(); ++ touch_file("/tmp/tmp_db", false, Some(0o777), None, None).unwrap(); + let mut f = OpenOptions::new().write(true).open("/tmp/tmp_db").unwrap(); + f.write_all(content.as_bytes()).unwrap(); + let device = Device::new(); +@@ -3675,19 +3675,22 @@ V:100 + } + + unlink("/tmp/tmp_db").unwrap(); ++ unlink("/tmp/tmp_db_writeonly").unwrap(); + } + + #[test] + fn test_set_is_initialized() { + let device = Device::from_subsystem_sysname("net", "lo").unwrap(); + device.set_is_initialized(); +- device ++ if device + .trigger_with_uuid(DeviceAction::Change, false) +- .unwrap(); +- device +- .trigger_with_uuid(DeviceAction::Change, true) +- .unwrap(); +- device.trigger(DeviceAction::Change).unwrap(); ++ .is_ok() ++ { ++ device ++ .trigger_with_uuid(DeviceAction::Change, true) ++ .unwrap(); ++ device.trigger(DeviceAction::Change).unwrap(); ++ } + } + + #[test] +@@ -3707,14 +3710,16 @@ V:100 + device.set_devmode("666").unwrap(); + device.set_diskseq("1").unwrap(); + device.set_action_from_string("change").unwrap(); +- device.set_sysattr_value("ifalias", Some("test")).unwrap(); ++ ++ if device.set_sysattr_value("ifalias", Some("test")).is_ok() { ++ assert_eq!(&device.get_cached_sysattr_value("ifalias").unwrap(), "test"); ++ } + + assert_eq!(&device.get_property_value("DEVUID").unwrap(), "1"); + assert_eq!(&device.get_property_value("DEVGID").unwrap(), "1"); + assert_eq!(&device.get_property_value("DEVMODE").unwrap(), "666"); + assert_eq!(&device.get_property_value("DISKSEQ").unwrap(), "1"); + assert_eq!(&device.get_property_value("ACTION").unwrap(), "change"); +- assert_eq!(&device.get_cached_sysattr_value("ifalias").unwrap(), "test"); + + assert!(device.set_devuid("invalid").is_err()); + assert!(device.set_devgid("invalid").is_err()); +-- +2.33.0 + diff --git a/backport-fix-device-keep-consistent-on-the-base-path-for-db-c.patch b/backport-fix-device-keep-consistent-on-the-base-path-for-db-c.patch new file mode 100644 index 0000000..adbc91c --- /dev/null +++ b/backport-fix-device-keep-consistent-on-the-base-path-for-db-c.patch @@ -0,0 +1,28 @@ +From 6188872fbc38cd38a3f2e909d9b4fe27473a8f67 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 21 Nov 2023 20:06:29 +0800 +Subject: [PATCH 078/103] fix(device): keep consistent on the base path for db + cloned device object + +The db cloned device object should collect data from the same base path with +the original device object. +--- + libs/device/src/device.rs | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index b8507fe0..e8e2dcc5 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -2390,6 +2390,8 @@ impl Device { + pub fn shallow_clone(&self) -> Result { + let device = Self::default(); + ++ device.set_base_path(self.base_path.borrow().as_str()); ++ + let syspath = self.get_syspath()?; + + device.set_syspath(&syspath, false)?; +-- +2.33.0 + diff --git a/backport-fix-device-replenish-feature-dependency-of-uuid-on-b.patch b/backport-fix-device-replenish-feature-dependency-of-uuid-on-b.patch new file mode 100644 index 0000000..8617a9e --- /dev/null +++ b/backport-fix-device-replenish-feature-dependency-of-uuid-on-b.patch @@ -0,0 +1,26 @@ +From 7c4bfcad52d1e8b2de7c6c2d5bfc1c08b401341f Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 02:10:37 +0800 +Subject: [PATCH 046/103] fix(device): replenish feature dependency of uuid on + basic crate + +uuid is used on trigger_with_uuid method. +--- + libs/device/Cargo.toml | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libs/device/Cargo.toml b/libs/device/Cargo.toml +index 6af6a404..ecb9955c 100644 +--- a/libs/device/Cargo.toml ++++ b/libs/device/Cargo.toml +@@ -12,6 +12,7 @@ basic = { path = "../basic", default-features = false, features = [ + "fs", + "fd", + "murmurhash2", ++ "uuid", + ] } + event = { path = "../event" } + log = { path = "../log" } +-- +2.33.0 + diff --git a/backport-fix-device-set-driver-subsystem-after-generating-dev.patch b/backport-fix-device-set-driver-subsystem-after-generating-dev.patch new file mode 100644 index 0000000..5d17f65 --- /dev/null +++ b/backport-fix-device-set-driver-subsystem-after-generating-dev.patch @@ -0,0 +1,92 @@ +From 400c6e270583c62724dd2bfd8b92ad96a2f0cfcb Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 22:59:44 +0800 +Subject: [PATCH 053/103] fix(device): set driver subsystem after generating + device from nulstr + +The driver subsystem is omitted when receiving device object from nulstr, +which leads to incorrect device id in /run/devmaster/data/ for driver +subsystems. +--- + libs/device/src/device.rs | 32 ++++++++++++++++++++++++++++--- + libs/device/src/device_monitor.rs | 5 +++-- + 2 files changed, 32 insertions(+), 5 deletions(-) + +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 4f5779ce..3845e21c 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -246,7 +246,29 @@ impl Device { + + device.update_properties_bufs()?; + +- Ok(device) ++ device.verify() ++ } ++ ++ /// Verify the legality of a device object from nulstr. ++ fn verify(self) -> Result { ++ if self.devpath.borrow().is_empty() ++ || self.subsystem.borrow().is_empty() ++ || *self.action.borrow() == DeviceAction::Invalid ++ || *self.seqnum.borrow() == 0 ++ { ++ return Err(Error::Nix { ++ msg: "Received invalid device object from uevent".to_string(), ++ source: Errno::EINVAL, ++ }); ++ } ++ ++ if &*self.subsystem.borrow() == "drivers" { ++ self.set_drivers_subsystem()?; ++ } ++ ++ self.sealed.replace(true); ++ ++ Ok(self) + } + + /// create a Device instance from devname +@@ -1237,7 +1259,7 @@ impl Device { + /// shadow clone a device object and import properties from db + pub fn clone_with_db(&self) -> Result { + let device = self.shallow_clone()?; +- device.read_db()?; ++ device.read_db_internal(true)?; + device.sealed.replace(true); + Ok(device) + } +@@ -3535,9 +3557,13 @@ mod tests { + let syspath = dev.get_syspath().unwrap(); + let devnum = dev.get_devnum().unwrap(); + let id = dev.get_device_id().unwrap(); +- let (nulstr, _) = dev.get_properties_nulstr().unwrap(); + let devname = dev.get_devname().unwrap(); + ++ dev.set_action_from_string("change").unwrap(); ++ dev.set_seqnum_from_string("1000").unwrap(); ++ ++ let (nulstr, _) = dev.get_properties_nulstr().unwrap(); ++ + let dev_new = Device::from_syspath(&syspath, true).unwrap(); + assert_eq!(dev, &dev_new); + +diff --git a/libs/device/src/device_monitor.rs b/libs/device/src/device_monitor.rs +index db51c40f..3f1d1ba6 100644 +--- a/libs/device/src/device_monitor.rs ++++ b/libs/device/src/device_monitor.rs +@@ -249,8 +249,9 @@ mod tests { + + /// + fn dispatch(&self, e: &Events) -> i32 { +- let device = self.device_monitor.receive_device().unwrap(); +- println!("{}", device.get_device_id().unwrap()); ++ if let Ok(device) = self.device_monitor.receive_device() { ++ println!("{}", device.get_device_id().unwrap()); ++ } + e.set_exit(); + 0 + } +-- +2.33.0 + diff --git a/backport-fix-device-use-feature-to-avoid-loopdev-being-compil.patch b/backport-fix-device-use-feature-to-avoid-loopdev-being-compil.patch new file mode 100644 index 0000000..b126dcd --- /dev/null +++ b/backport-fix-device-use-feature-to-avoid-loopdev-being-compil.patch @@ -0,0 +1,138 @@ +From 19d481dbf79337924cb0a389cb2e073e80ce5495 Mon Sep 17 00:00:00 2001 +From: root +Date: Wed, 6 Dec 2023 20:53:59 +0800 +Subject: [PATCH 1/1] loopdev + +--- + Cargo.lock | 1 + + exts/devmaster/Cargo.toml | 5 ++++- + libs/device/Cargo.toml | 6 ++++-- + libs/device/src/device.rs | 9 +++++---- + libs/device/src/utils.rs | 9 +++++++-- + 5 files changed, 21 insertions(+), 9 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index f103489..616a69c 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -378,6 +378,7 @@ version = "0.5.1" + dependencies = [ + "basic", + "bitflags", ++ "device", + "event", + "fnmatch-sys", + "libc", +diff --git a/exts/devmaster/Cargo.toml b/exts/devmaster/Cargo.toml +index c52e988..f9f2961 100644 +--- a/exts/devmaster/Cargo.toml ++++ b/exts/devmaster/Cargo.toml +@@ -34,7 +34,7 @@ basic = { path = "../../libs/basic", default-features = false, features = [ + "argv", + ] } + blkid_rs = { path = "../../libs/blkid_rs" } +-device = { path = "../../libs/device" } ++device = { path = "../../libs/device", default-features = false } + event = { path = "../../libs/event" } + input_event_codes_rs = { path = "../../libs/input_event_codes_rs" } + kmod_rs = { path = "../../libs/kmod_rs" } +@@ -78,3 +78,6 @@ fnmatch-sys = "1.0.0" + + [build-dependencies] + basic = { path = "../../libs/basic", features = ["cargo"] } ++ ++[dev-dependencies] ++device = { path = "../../libs/device", features = ["loopdev"] } +diff --git a/libs/device/Cargo.toml b/libs/device/Cargo.toml +index ecb9955..1737c17 100644 +--- a/libs/device/Cargo.toml ++++ b/libs/device/Cargo.toml +@@ -20,8 +20,7 @@ log = { path = "../log" } + # third libraries + bitflags = "1.3.2" + libc = { default-features = false, version = "0.2.140" } +-# only used in test case +-loopdev = "0.4.0" ++loopdev = { version = "0.4.0", optional = true } # only used in test case + nix = { default-features = false, version = "0.24", features = [ + "ioctl", + "user", +@@ -32,3 +31,6 @@ nix = { default-features = false, version = "0.24", features = [ + ] } + snafu = { default-features = false, version = "0.7" } + fnmatch-sys = "1.0.0" ++ ++[dev-dependencies] ++device = { path = ".", features = ["loopdev"] } +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index d1ab230..c11de5b 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -2845,16 +2845,17 @@ impl PartialEq for Device { + + #[cfg(test)] + mod tests { +- use std::fs::OpenOptions; +- use std::panic::catch_unwind; +- + use crate::{ + device::*, + device_enumerator::{DeviceEnumerationType, DeviceEnumerator}, +- utils::LoopDev, + }; + use basic::IN_SET; + use libc::S_IFBLK; ++ use std::fs::OpenOptions; ++ use std::panic::catch_unwind; ++ ++ #[cfg(feature = "loopdev")] ++ use crate::utils::LoopDev; + + fn compare(dev1: &Device, dev2: &Device) -> bool { + let syspath_1 = dev1.get_syspath().unwrap(); +diff --git a/libs/device/src/utils.rs b/libs/device/src/utils.rs +index df750ed..2b814fa 100644 +--- a/libs/device/src/utils.rs ++++ b/libs/device/src/utils.rs +@@ -11,12 +11,14 @@ + // See the Mulan PSL v2 for more details. + + //! utilities for device operation ++use crate::{error::*, Device}; + use nix::errno::Errno; ++use std::{cmp::Ordering, fmt::Debug, fs::DirEntry, path::Path}; + +-use crate::{error::*, Device}; ++#[cfg(feature = "loopdev")] + use loopdev::*; ++#[cfg(feature = "loopdev")] + use std::path::PathBuf; +-use std::{cmp::Ordering, fmt::Debug, fs::DirEntry, path::Path}; + + /// compare sound device + pub(crate) fn sound_device_compare(devpath_a: &str, devpath_b: &str) -> Ordering { +@@ -106,11 +108,13 @@ pub(crate) fn readlink_value + Debug>(path: P) -> Result +Date: Fri, 17 Nov 2023 07:44:31 +0800 +Subject: [PATCH 071/103] fix(devmaster): CONST can only take arch or virt as + attribute + +The inverse operator '!' is missed previously. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index eee77e63..57ff19ac 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -832,7 +832,7 @@ impl RuleToken { + } + } + "CONST" => { +- if attr.is_none() || matches!(attr.as_ref().unwrap().as_str(), "arch" | "virt") { ++ if attr.is_none() || !matches!(attr.as_ref().unwrap().as_str(), "arch" | "virt") { + return Err(Error::RulesLoadError { + msg: "Key 'CONST' has invalid attribute.".location(&context), + }); +-- +2.33.0 + diff --git a/backport-fix-devmaster-adjust-temporary-file-permissions.patch b/backport-fix-devmaster-adjust-temporary-file-permissions.patch new file mode 100644 index 0000000..cca242a --- /dev/null +++ b/backport-fix-devmaster-adjust-temporary-file-permissions.patch @@ -0,0 +1,97 @@ +From 26a93ec411098bf29fa8ebe9b84940f8c9455423 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 31 Oct 2023 20:24:30 +0800 +Subject: [PATCH 014/103] fix(devmaster): adjust temporary file permissions + +Adjust temporary file permissions. +--- + exts/devmaster/src/lib/rules/node.rs | 10 +++++++++- + libs/device/src/device.rs | 22 +++++++++++++++++++--- + 2 files changed, 28 insertions(+), 4 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/node.rs b/exts/devmaster/src/lib/rules/node.rs +index 45b8b36b..9fba906e 100644 +--- a/exts/devmaster/src/lib/rules/node.rs ++++ b/exts/devmaster/src/lib/rules/node.rs +@@ -33,7 +33,7 @@ + //! directory, the directory will be removed. + + use crate::{error::*, log_dev, log_dev_option}; +-use basic::fs_util::path_simplify; ++use basic::fs_util::{chmod, path_simplify}; + use basic::fs_util::{fchmod_and_chown, futimens_opath, symlink}; + use basic::{fd_util::xopendirat, fs_util::remove_dir_until}; + use cluFlock::ExclusiveFlock; +@@ -355,6 +355,14 @@ pub(crate) fn open_prior_dir(symlink: &str) -> Result<(Dir, File)> { + }) + .log_error(&format!("failed to create directory all '{}'", dirname))?; + ++ if let Err(e) = chmod(dirname.as_str(), 0o750) { ++ log::error!("Failed to set permission for {}: {}", &dirname, e); ++ } ++ ++ if let Err(e) = chmod("/run/devmaster/links", 0o750) { ++ log::error!("Failed to set permission for /run/devmaster/links: {}", e); ++ } ++ + let dir = nix::dir::Dir::from_fd( + nix::fcntl::open( + dirname.as_str(), +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 5b1eff1f..5a95e0f5 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -15,7 +15,7 @@ + use crate::err_wrapper; + use crate::utils::readlink_value; + use crate::{error::*, DeviceAction}; +-use basic::fs_util::{open_temporary, touch_file}; ++use basic::fs_util::{chmod, open_temporary, touch_file}; + use basic::parse::{device_path_parse_devnum, parse_devnum, parse_ifindex}; + use libc::{ + dev_t, faccessat, gid_t, mode_t, uid_t, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, +@@ -1532,6 +1532,10 @@ impl Device { + .map_or_else(|| nix::Error::EIO, nix::Error::from_i32), + })?; + ++ if let Err(e) = chmod(DB_BASE_DIR, 0o750) { ++ log::error!("Failed to set permission for /run/devmaster/data/: {}", e); ++ } ++ + let (mut file, tmp_file) = open_temporary(&db_path).map_err(|e| { + let errno = match e { + basic::error::Error::Nix { source } => source, +@@ -1546,9 +1550,9 @@ impl Device { + fchmod( + file.as_raw_fd(), + if *self.db_persist.borrow() { +- Mode::from_bits(0o1644).unwrap() ++ Mode::from_bits(0o1640).unwrap() + } else { +- Mode::from_bits(0o644).unwrap() ++ Mode::from_bits(0o640).unwrap() + }, + ) + .map_err(|e| { +@@ -1697,6 +1701,18 @@ impl Device { + source: nix::Error::EINVAL, + })?; + ++ if let Err(e) = chmod(TAGS_BASE_DIR, 0o750) { ++ log::error!("Failed to set permission for {}: {}", TAGS_BASE_DIR, e); ++ } ++ ++ if let Err(e) = chmod(&format!("{}{}", TAGS_BASE_DIR, tag), 0o750) { ++ log::error!( ++ "Failed to set permission for {}: {}", ++ format!("{}{}", TAGS_BASE_DIR, tag), ++ e ++ ); ++ } ++ + return Ok(()); + } + +-- +2.33.0 + diff --git a/backport-fix-devmaster-append-zero-to-the-file-name.patch b/backport-fix-devmaster-append-zero-to-the-file-name.patch new file mode 100644 index 0000000..22475e7 --- /dev/null +++ b/backport-fix-devmaster-append-zero-to-the-file-name.patch @@ -0,0 +1,27 @@ +From 498398f0b59824726a9bc0bbc4c53af5c8b7b4a2 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 2 Nov 2023 18:14:40 +0800 +Subject: [PATCH 021/103] fix(devmaster): append zero to the file name + +The file name is transferred to 'faccessat', which should be a C +like string that ends with zero. +--- + exts/devmaster/src/lib/builtin/net_id.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/exts/devmaster/src/lib/builtin/net_id.rs b/exts/devmaster/src/lib/builtin/net_id.rs +index 2b2cc59b..e5f1faa6 100644 +--- a/exts/devmaster/src/lib/builtin/net_id.rs ++++ b/exts/devmaster/src/lib/builtin/net_id.rs +@@ -401,7 +401,7 @@ fn parse_hotplug_slot_from_function_id( + }); + } + +- let filename = format!("{:08}", function_id); ++ let filename = format!("{:08}\0", function_id); + + if unsafe { faccessat(slots_dirfd, filename.as_ptr() as *const c_char, F_OK, 0) } < 0 { + log_dev!( +-- +2.33.0 + diff --git a/backport-fix-devmaster-avoid-potential-test-case-failures.patch b/backport-fix-devmaster-avoid-potential-test-case-failures.patch new file mode 100644 index 0000000..60cfd91 --- /dev/null +++ b/backport-fix-devmaster-avoid-potential-test-case-failures.patch @@ -0,0 +1,265 @@ +From ecab786d75eaf3ab6a110f1d6b47c1b1d7e2ec01 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 21 Nov 2023 15:17:23 +0800 +Subject: [PATCH 076/103] fix(devmaster): avoid potential test case failures + +Sharing the same temporary rules directory in multiple test cases +may lead to weird and occasional failures. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 123 ++++++++++++--------- + 1 file changed, 69 insertions(+), 54 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index e9dd2259..999c9692 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -1736,14 +1736,24 @@ mod tests { + use std::panic::catch_unwind; + use std::{fs, path::Path}; + +- fn create_tmp_rules(dir: &'static str, file: &str, content: &str) { ++ fn create_tmp_rules(dir: &'static str, file: &str, content: &str, truncate: bool) { + assert!(fs::create_dir_all(dir).is_ok()); +- assert!(fs::write(format!("{}/{}", dir, file), content,).is_ok()); ++ let s = format!("{}/{}", dir, file); ++ let p = Path::new(&s); ++ assert!(fs::write(p, content,).is_ok()); ++ let mut f = fs::OpenOptions::new() ++ .write(true) ++ .truncate(truncate) ++ .open(p) ++ .unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); ++ f.flush().unwrap(); ++ while !p.exists() {} + } + + fn clear_tmp_rules(dir: &'static str) { + if Path::new(dir).exists() { +- assert!(fs::remove_dir_all(dir).is_ok()); ++ fs::remove_dir_all(dir).unwrap(); + } + } + +@@ -1758,7 +1768,7 @@ mod tests { + 0, + false, + ); +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_load_rules"); + + let legal_rule = vec![ + "ACTION == \"change\", SYMLINK += \"test1\"", // Test legal rules. +@@ -1810,23 +1820,21 @@ mod tests { + "SECLABEL{x}:=\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. + ]; + +- create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); +- + for &content in legal_rule.iter() { +- let mut f = fs::OpenOptions::new() +- .write(true) +- .truncate(true) +- .open("/tmp/devmaster/rules/00-test.rules") +- .unwrap(); +- f.write_all(content.as_bytes()).unwrap(); ++ create_tmp_rules( ++ "/tmp/devmaster/test_load_rules", ++ "00-test.rules", ++ content, ++ true, ++ ); + + let _ = Rules::load_rules( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_load_rules".to_string()], + ResolveNameTime::Early, + ); + } + +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_load_rules"); + } + + #[test] +@@ -1840,7 +1848,7 @@ mod tests { + 0, + false, + ); +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_load_rules_panic"); + + let illegal_rule = vec![ + "action==\"change\"", // Error in State::Pre +@@ -1945,26 +1953,24 @@ mod tests { + "XXX=\"xxx\"", // Invalid token key. + ]; + +- create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); +- + for content in illegal_rule.iter() { +- let mut f = fs::OpenOptions::new() +- .write(true) +- .truncate(true) +- .open("/tmp/devmaster/rules/00-test.rules") +- .unwrap(); +- f.write_all(content.as_bytes()).unwrap(); ++ create_tmp_rules( ++ "/tmp/devmaster/test_load_rules_panic", ++ "00-test.rules", ++ content, ++ true, ++ ); + + assert!(catch_unwind(|| { + let _ = Rules::load_rules( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_load_rules_panic".to_string()], + ResolveNameTime::Early, + ); + }) + .is_err()); + } + +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_load_rules_panic"); + } + + #[test] +@@ -1978,50 +1984,48 @@ mod tests { + 0, + false, + ); +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_resolve_name_time"); + + let legal = vec!["OWNER=\"root\"", "GROUP=\"root\""]; + let illegal = vec!["OWNER=\"xxxx\"", "GROUP=\"xxxx\""]; + +- create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); +- + for &content in legal.iter() { +- let mut f = fs::OpenOptions::new() +- .write(true) +- .truncate(true) +- .open("/tmp/devmaster/rules/00-test.rules") +- .unwrap(); +- f.write_all(content.as_bytes()).unwrap(); ++ create_tmp_rules( ++ "/tmp/devmaster/test_resolve_name_time", ++ "00-test.rules", ++ content, ++ true, ++ ); + + let _ = Rules::load_rules( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_resolve_name_time".to_string()], + ResolveNameTime::Early, + ); + } + + for &content in illegal.iter() { +- let mut f = fs::OpenOptions::new() +- .write(true) +- .truncate(true) +- .open("/tmp/devmaster/rules/00-test.rules") +- .unwrap(); +- f.write_all(content.as_bytes()).unwrap(); ++ create_tmp_rules( ++ "/tmp/devmaster/test_resolve_name_time", ++ "00-test.rules", ++ content, ++ true, ++ ); + + let _ = Rules::load_rules( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_resolve_name_time".to_string()], + ResolveNameTime::Late, + ); + + assert!(catch_unwind(|| { + let _ = Rules::load_rules( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_resolve_name_time".to_string()], + ResolveNameTime::Early, + ); + }) + .is_err()); + } + +- clear_tmp_rules("/tmp/devmaster/rules"); ++ clear_tmp_rules("/tmp/devmaster/test_resolve_name_time"); + } + + #[test] +@@ -2330,11 +2334,11 @@ mod tests { + + #[test] + fn test_parse_rules() { +- create_dir_all("/tmp/devmaster/rules").unwrap(); ++ create_dir_all("/tmp/devmaster/test_parse_rules").unwrap(); + + /* Normal rule file. */ + touch_file( +- "/tmp/devmaster/rules/00-a.rules", ++ "/tmp/devmaster/test_parse_rules/00-a.rules", + false, + Some(0o777), + None, +@@ -2342,10 +2346,17 @@ mod tests { + ) + .unwrap(); + /* Skip parsing the file with invalid suffix. */ +- touch_file("/tmp/devmaster/rules/01-b", false, Some(0o777), None, None).unwrap(); ++ touch_file( ++ "/tmp/devmaster/test_parse_rules/01-b", ++ false, ++ Some(0o777), ++ None, ++ None, ++ ) ++ .unwrap(); + /* Failed to parse the file as it is not readable. */ + touch_file( +- "/tmp/devmaster/rules/02-c.rules", ++ "/tmp/devmaster/test_parse_rules/02-c.rules", + false, + Some(0o000), + None, +@@ -2354,21 +2365,25 @@ mod tests { + .unwrap(); + + let rules = Arc::new(RwLock::new(Rules::new( +- vec!["/tmp/devmaster/rules".to_string()], ++ vec!["/tmp/devmaster/test_parse_rules".to_string()], + ResolveNameTime::Never, + ))); + +- // Rules::parse_rules(Arc::new(RwLock::new(rules))); +- +- RuleFile::load_file("/tmp/devmaster/rules/00-a.rules".to_string(), rules.clone()); ++ RuleFile::load_file( ++ "/tmp/devmaster/test_parse_rules/00-a.rules".to_string(), ++ rules.clone(), ++ ); + + if nix::unistd::getuid().as_raw() != 0 { + assert!(catch_unwind(|| { +- RuleFile::load_file("/tmp/devmaster/rules/02-c.rules".to_string(), rules.clone()); ++ RuleFile::load_file( ++ "/tmp/devmaster/test_parse_rules/02-c.rules".to_string(), ++ rules.clone(), ++ ); + }) + .is_err()); + } + +- remove_dir_all("/tmp/devmaster").unwrap(); ++ remove_dir_all("/tmp/devmaster/test_parse_rules").unwrap(); + } + } +-- +2.33.0 + diff --git a/backport-fix-devmaster-cache-the-parsed-user-and-group.patch b/backport-fix-devmaster-cache-the-parsed-user-and-group.patch new file mode 100644 index 0000000..20e9680 --- /dev/null +++ b/backport-fix-devmaster-cache-the-parsed-user-and-group.patch @@ -0,0 +1,64 @@ +From 0ee8078d05e8fd67251b20383fc5b3cf04aedfb1 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 17 Nov 2023 01:20:21 +0800 +Subject: [PATCH 069/103] fix(devmaster): cache the parsed user and group + +The previously parsed user and group should be cached to avoid repeatedly +parsed later. Also fix and open the corresponding test case that is ignored +previously. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 21 ++++++++++++++------- + 1 file changed, 14 insertions(+), 7 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index 19b7aaa4..a4535b52 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -133,7 +133,10 @@ impl Rules { + + match User::from_name(username) { + Ok(user) => match user { +- Some(u) => Ok(u), ++ Some(u) => { ++ self.users.insert(username.to_string(), u.clone()); ++ Ok(u) ++ } + None => Err(Error::RulesLoadError { + msg: format!("The user name {} has no credential.", username), + }), +@@ -152,7 +155,10 @@ impl Rules { + + match Group::from_name(groupname) { + Ok(group) => match group { +- Some(g) => Ok(g), ++ Some(g) => { ++ self.groups.insert(groupname.to_string(), g.clone()); ++ Ok(g) ++ } + None => Err(Error::RulesLoadError { + msg: format!("The group name {} has no credential.", groupname), + }), +@@ -1983,14 +1989,15 @@ SYMLINK += \"test111111\"", + } + + #[test] +- #[ignore] +- fn test_resolve_user() { ++ fn test_resolve_user_group() { + let mut rules = Rules::new(vec![], ResolveNameTime::Early); +- assert!(rules.resolve_user("tss").is_ok()); + assert!(rules.resolve_user("root").is_ok()); +- assert!(rules.users.contains_key("tss")); + assert!(rules.users.contains_key("root")); +- assert!(rules.resolve_user("cjy").is_err()); ++ assert!(rules.resolve_user("abcdefg").is_err()); ++ ++ assert!(rules.resolve_group("root").is_ok()); ++ assert!(rules.groups.contains_key("root")); ++ assert!(rules.resolve_group("abcdefg").is_err()); + } + + #[test] +-- +2.33.0 + diff --git a/backport-fix-devmaster-check-the-validity-of-kmod-context-dur.patch b/backport-fix-devmaster-check-the-validity-of-kmod-context-dur.patch new file mode 100644 index 0000000..61d8f1e --- /dev/null +++ b/backport-fix-devmaster-check-the-validity-of-kmod-context-dur.patch @@ -0,0 +1,123 @@ +From f923eda87854adb2d245f43ba3656b0fb46fe7c1 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 3 Nov 2023 01:47:16 +0800 +Subject: [PATCH 028/103] fix(devmaster): check the validity of kmod context + during new method + +The kmod_sys::kmod_new may return a null pointer. Check the validity +of the pointer before using it to constuct the kmod instance. +--- + exts/devmaster/src/lib/builtin/kmod.rs | 21 +++++++++++++++++---- + libs/kmod_rs/src/lib.rs | 17 ++++++++--------- + 2 files changed, 25 insertions(+), 13 deletions(-) + +diff --git a/exts/devmaster/src/lib/builtin/kmod.rs b/exts/devmaster/src/lib/builtin/kmod.rs +index ac73d1bb..cf718d41 100644 +--- a/exts/devmaster/src/lib/builtin/kmod.rs ++++ b/exts/devmaster/src/lib/builtin/kmod.rs +@@ -24,14 +24,14 @@ use std::rc::Rc; + /// kmod builtin command + pub(crate) struct Kmod { + /// kmod struct +- kernel_module: Rc>, ++ kernel_module: Option>>, + } + + impl Kmod { + /// create Kmod + pub(crate) fn new() -> Kmod { + Kmod { +- kernel_module: Rc::new(RefCell::new(kmod_rs::LibKmod::new())), ++ kernel_module: kmod_rs::LibKmod::new().map(|inner| Rc::new(RefCell::new(inner))), + } + } + } +@@ -53,7 +53,8 @@ impl Builtin for Kmod { + ) -> Result { + let device = exec_unit.get_device(); + +- if self.kernel_module.borrow().is_ctx_null() { ++ if self.kernel_module.is_none() { ++ log::error!("Kmod context is not loaded."); + return Ok(true); + } + +@@ -72,6 +73,8 @@ impl Builtin for Kmod { + if !modalias.is_empty() { + if let Err(e) = self + .kernel_module ++ .as_ref() ++ .unwrap() + .borrow_mut() + .module_load_and_warn(&modalias, false) + { +@@ -82,6 +85,8 @@ impl Builtin for Kmod { + for i in 2..argc { + if let Err(e) = self + .kernel_module ++ .as_ref() ++ .unwrap() + .borrow_mut() + .module_load_and_warn(&argv[i as usize], false) + { +@@ -94,7 +99,13 @@ impl Builtin for Kmod { + + /// builtin init function + fn init(&self) { +- if let Err(e) = self.kernel_module.borrow_mut().load_resources() { ++ if let Err(e) = self ++ .kernel_module ++ .as_ref() ++ .unwrap() ++ .borrow_mut() ++ .load_resources() ++ { + log::error!("Load resources failed! {}", e); + } + } +@@ -105,6 +116,8 @@ impl Builtin for Kmod { + /// check whether builtin command should reload + fn should_reload(&self) -> bool { + self.kernel_module ++ .as_ref() ++ .unwrap() + .borrow_mut() + .validate_resources() + .map_or(false, |e| { +diff --git a/libs/kmod_rs/src/lib.rs b/libs/kmod_rs/src/lib.rs +index d0efabc5..f95cd32a 100644 +--- a/libs/kmod_rs/src/lib.rs ++++ b/libs/kmod_rs/src/lib.rs +@@ -73,21 +73,20 @@ impl Drop for LibKmod { + } + } + +-impl Default for LibKmod { +- fn default() -> Self { +- Self::new() +- } +-} +- + impl LibKmod { + /// Create libkmod +- pub fn new() -> LibKmod { ++ pub fn new() -> Option { + let c = unsafe { kmod_sys::kmod_new(std::ptr::null(), std::ptr::null()) }; +- LibKmod { ++ ++ if c.is_null() { ++ return None; ++ } ++ ++ Some(LibKmod { + ctx: c, + kmod_list_head: ptr::null::() as *mut kmod_sys::kmod_list, + module: ptr::null::() as *mut kmod_sys::kmod_module, +- } ++ }) + } + + /// Create KmodListIter with internal members +-- +2.33.0 + diff --git a/backport-fix-devmaster-drop-incorrect-preventation-on-removal.patch b/backport-fix-devmaster-drop-incorrect-preventation-on-removal.patch new file mode 100644 index 0000000..3ca81f8 --- /dev/null +++ b/backport-fix-devmaster-drop-incorrect-preventation-on-removal.patch @@ -0,0 +1,31 @@ +From adf0de497132a30577cd38c6a9e0079c5224c279 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 17 Nov 2023 06:12:36 +0800 +Subject: [PATCH 070/103] fix(devmaster): drop incorrect preventation on + removal op for SYMLINK token + +The preventation code makes the rules loading panic when SYMLINK takes +the removal assignment op, which is incorrect. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index a4535b52..eee77e63 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -693,11 +693,6 @@ impl RuleToken { + msg: "Key 'SYMLINK' can not carry attribute.".location(&context), + }); + } +- if op == OperatorType::Remove { +- return Err(Error::RulesLoadError { +- msg: "Key 'SYMLINK' can not take remove operator.".location(&context), +- }); +- } + + if !op_is_match { + if let Err(e) = check_value_format(key.as_str(), value.as_str(), false) { +-- +2.33.0 + diff --git a/backport-fix-devmaster-drop-unnecessary-debug-trait-implement.patch b/backport-fix-devmaster-drop-unnecessary-debug-trait-implement.patch new file mode 100644 index 0000000..d38904c --- /dev/null +++ b/backport-fix-devmaster-drop-unnecessary-debug-trait-implement.patch @@ -0,0 +1,107 @@ +From 4c23e696a6bbf19f4f0806501af04aaf606ab3fa Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 14:28:35 +0800 +Subject: [PATCH 050/103] fix(devmaster): drop unnecessary debug trait + implementations + +The debug trait derivations will break the compilation. +--- + .../src/lib/framework/control_manager.rs | 1 - + .../src/lib/framework/garbage_collect.rs | 2 -- + .../src/lib/framework/worker_manager.rs | 2 -- + libs/device/src/error.rs | 16 +++------------- + 4 files changed, 3 insertions(+), 18 deletions(-) + +diff --git a/exts/devmaster/src/lib/framework/control_manager.rs b/exts/devmaster/src/lib/framework/control_manager.rs +index 75fae984..342c6a0c 100644 +--- a/exts/devmaster/src/lib/framework/control_manager.rs ++++ b/exts/devmaster/src/lib/framework/control_manager.rs +@@ -30,7 +30,6 @@ use std::{ + pub const CONTROL_MANAGER_LISTEN_ADDR: &str = "/run/devmaster/control"; + + /// control manager +-#[derive(Debug)] + pub struct ControlManager { + /// listener for devctl messages + listener: RefCell, +diff --git a/exts/devmaster/src/lib/framework/garbage_collect.rs b/exts/devmaster/src/lib/framework/garbage_collect.rs +index 7a12ede8..c154f430 100644 +--- a/exts/devmaster/src/lib/framework/garbage_collect.rs ++++ b/exts/devmaster/src/lib/framework/garbage_collect.rs +@@ -25,7 +25,6 @@ use std::{ + /// max time interval for idle worker + const WORKER_MAX_IDLE_INTERVAL: u64 = 3; + +-#[derive(Debug)] + pub(crate) struct GarbageCollect { + devmaster: Weak>, + +@@ -128,7 +127,6 @@ impl Source for GarbageCollect { + } + + /// kill idle workers +-#[derive(Debug)] + pub(crate) struct IdleWorkerKiller { + /// time interval + pub(crate) time: u64, +diff --git a/exts/devmaster/src/lib/framework/worker_manager.rs b/exts/devmaster/src/lib/framework/worker_manager.rs +index c7c1d45f..870f6779 100644 +--- a/exts/devmaster/src/lib/framework/worker_manager.rs ++++ b/exts/devmaster/src/lib/framework/worker_manager.rs +@@ -47,7 +47,6 @@ pub(crate) enum WorkerMessage { + } + + /// worker manager +-#[derive(Debug)] + pub struct WorkerManager { + /// max number of workers + pub(crate) workers_capacity: u32, +@@ -65,7 +64,6 @@ pub struct WorkerManager { + } + + /// worker +-#[derive(Debug)] + pub struct Worker { + /// worker unique id + id: u32, +diff --git a/libs/device/src/error.rs b/libs/device/src/error.rs +index 10410301..2cea6f4b 100644 +--- a/libs/device/src/error.rs ++++ b/libs/device/src/error.rs +@@ -19,22 +19,13 @@ use snafu::prelude::Snafu; + #[derive(Debug, Snafu)] + #[snafu(visibility(pub))] + #[non_exhaustive] ++#[allow(missing_docs)] + pub enum Error { +- /// other error + #[snafu(context, display("Device error: {}", msg))] +- Nix { +- /// message +- msg: String, +- /// errno indicates the error kind +- source: nix::Error, +- }, ++ Nix { msg: String, source: nix::Error }, + + #[snafu(context, display("IO error: {}", msg))] +- Io { +- /// message +- msg: String, +- source: std::io::Error, +- }, ++ Io { msg: String, source: std::io::Error }, + + #[snafu(context, display("Basic error: {}", msg))] + Basic { msg: String, source: basic::Error }, +@@ -106,7 +97,6 @@ impl Error { + } + } + +- + #[cfg(test)] + mod test { + use super::*; +-- +2.33.0 + diff --git a/backport-fix-devmaster-fix-misspelt-sysattr-sas_address.patch b/backport-fix-devmaster-fix-misspelt-sysattr-sas_address.patch new file mode 100644 index 0000000..91c12b4 --- /dev/null +++ b/backport-fix-devmaster-fix-misspelt-sysattr-sas_address.patch @@ -0,0 +1,35 @@ +From f309964ae6101b94b2dd3c90f6e363e175685301 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 3 Nov 2023 01:11:39 +0800 +Subject: [PATCH 027/103] fix(devmaster): fix misspelt sysattr sas_address + +The 'sas_address' attribte is misspelt. +--- + exts/devmaster/src/lib/builtin/path_id.rs | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/exts/devmaster/src/lib/builtin/path_id.rs b/exts/devmaster/src/lib/builtin/path_id.rs +index ecd730f8..303b7528 100644 +--- a/exts/devmaster/src/lib/builtin/path_id.rs ++++ b/exts/devmaster/src/lib/builtin/path_id.rs +@@ -390,7 +390,7 @@ impl PathId { + Err(_) => return None, + }; + +- let sas_address = match asadev.get_sysattr_value("asa_address") { ++ let sas_address = match asadev.get_sysattr_value("sas_address") { + Ok(addr) => addr, + Err(_) => return None, + }; +@@ -430,7 +430,7 @@ impl PathId { + Err(_) => return None, + }; + +- let target = match sessiondev.get_sysattr_value("asa_address") { ++ let target = match sessiondev.get_sysattr_value("sas_address") { + Ok(port) => port, + Err(_) => return None, + }; +-- +2.33.0 + diff --git a/backport-fix-devmaster-fix-out-of-bounds-in-net_id.patch b/backport-fix-devmaster-fix-out-of-bounds-in-net_id.patch new file mode 100644 index 0000000..1533409 --- /dev/null +++ b/backport-fix-devmaster-fix-out-of-bounds-in-net_id.patch @@ -0,0 +1,27 @@ +From 84d4b4fcf0b2809e1857be3c17099ba1c41d7ea7 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 2 Nov 2023 21:44:50 +0800 +Subject: [PATCH 023/103] fix(devmaster): fix out-of-bounds in net_id + +The pci subclass consists of two numbers, but is captured with three +length by mistake. +--- + exts/devmaster/src/lib/builtin/net_id.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/exts/devmaster/src/lib/builtin/net_id.rs b/exts/devmaster/src/lib/builtin/net_id.rs +index e5f1faa6..091d6ccd 100644 +--- a/exts/devmaster/src/lib/builtin/net_id.rs ++++ b/exts/devmaster/src/lib/builtin/net_id.rs +@@ -338,7 +338,7 @@ fn is_pci_bridge(dev: Rc>) -> bool { + + /* PCI device subclass 04 corresponds to PCI bridge */ + // modalias[idx+2:2 +- let pci_subclass = match modalias.get(idx + 2..idx + 5) { ++ let pci_subclass = match modalias.get(idx + 2..idx + 4) { + Some(s) => s, + None => return false, + }; +-- +2.33.0 + diff --git a/backport-fix-devmaster-fix-potential-integer-overflow-in-scsi.patch b/backport-fix-devmaster-fix-potential-integer-overflow-in-scsi.patch new file mode 100644 index 0000000..420ef70 --- /dev/null +++ b/backport-fix-devmaster-fix-potential-integer-overflow-in-scsi.patch @@ -0,0 +1,28 @@ +From f57218f2439ec7f0920af7f9e446ce3449944c7c Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 2 Nov 2023 19:47:28 +0800 +Subject: [PATCH 022/103] fix(devmaster): fix potential integer overflow in + scsi_id + +The buffer is a u8 vector, whose element may overflow if it add 4 +before changing type to usize. +--- + exts/devmaster/src/bin/tools/scsi_id/main.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/exts/devmaster/src/bin/tools/scsi_id/main.rs b/exts/devmaster/src/bin/tools/scsi_id/main.rs +index 8098c8bf..0e621cd3 100644 +--- a/exts/devmaster/src/bin/tools/scsi_id/main.rs ++++ b/exts/devmaster/src/bin/tools/scsi_id/main.rs +@@ -1158,7 +1158,7 @@ fn do_scsi_page80_inquiry( + return 1; + } + +- let len: usize = (buffer[3] + 4) as usize; ++ let len: usize = buffer[3] as usize + 4; + + if get_serial { + dev_scsi.serial = "S".to_string(); +-- +2.33.0 + diff --git a/backport-fix-devmaster-keep-the-same-name-with-udev-control-s.patch b/backport-fix-devmaster-keep-the-same-name-with-udev-control-s.patch new file mode 100644 index 0000000..1e99a44 --- /dev/null +++ b/backport-fix-devmaster-keep-the-same-name-with-udev-control-s.patch @@ -0,0 +1,43 @@ +From 9f50a5627a9cfb9b6dba4eeaa524d8986f2408b8 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 30 Oct 2023 20:58:53 +0800 +Subject: [PATCH 008/103] fix(devmaster): keep the same name with udev control + socket + +The libudev api will check the /run/udev/control socket to indicate +wether udevd is running. Keep the name of control socket same with +udev. +--- + exts/devmaster/src/lib/framework/control_manager.rs | 2 +- + exts/devmaster/src/lib/framework/worker_manager.rs | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/exts/devmaster/src/lib/framework/control_manager.rs b/exts/devmaster/src/lib/framework/control_manager.rs +index 38c7c337..75fae984 100644 +--- a/exts/devmaster/src/lib/framework/control_manager.rs ++++ b/exts/devmaster/src/lib/framework/control_manager.rs +@@ -27,7 +27,7 @@ use std::{ + }; + + /// listening address for control manager +-pub const CONTROL_MANAGER_LISTEN_ADDR: &str = "/run/devmaster/control.sock"; ++pub const CONTROL_MANAGER_LISTEN_ADDR: &str = "/run/devmaster/control"; + + /// control manager + #[derive(Debug)] +diff --git a/exts/devmaster/src/lib/framework/worker_manager.rs b/exts/devmaster/src/lib/framework/worker_manager.rs +index 55778ddb..c7c1d45f 100644 +--- a/exts/devmaster/src/lib/framework/worker_manager.rs ++++ b/exts/devmaster/src/lib/framework/worker_manager.rs +@@ -38,7 +38,7 @@ use std::{ + use super::devmaster::Devmaster; + + /// worker manager listen address +-pub const WORKER_MANAGER_LISTEN_ADDR: &str = "/run/devmaster/worker.sock"; ++pub const WORKER_MANAGER_LISTEN_ADDR: &str = "/run/devmaster/worker"; + + /// messages sended by manager to workers + pub(crate) enum WorkerMessage { +-- +2.33.0 + diff --git a/backport-fix-return-Ok-when-the-.hwdb-and-hwdb.bin-files-cann.patch b/backport-fix-return-Ok-when-the-.hwdb-and-hwdb.bin-files-cann.patch new file mode 100644 index 0000000..9611e75 --- /dev/null +++ b/backport-fix-return-Ok-when-the-.hwdb-and-hwdb.bin-files-cann.patch @@ -0,0 +1,26 @@ +From 0df31891bfd0ab047d29afbd2313bd98bcf69a98 Mon Sep 17 00:00:00 2001 +From: huyubiao +Date: Mon, 6 Nov 2023 20:13:11 +0800 +Subject: [PATCH 033/103] fix: return Ok when the .hwdb and hwdb.bin files + cannot be found + +--- + libs/hwdb/src/hwdb_util.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libs/hwdb/src/hwdb_util.rs b/libs/hwdb/src/hwdb_util.rs +index 2ff6243a..a5c2eac0 100644 +--- a/libs/hwdb/src/hwdb_util.rs ++++ b/libs/hwdb/src/hwdb_util.rs +@@ -753,7 +753,7 @@ impl HwdbUtil { + hwdb_bin + ), + Err(e) => { +- if e == nix::Error::ENOENT { ++ if e != nix::Error::ENOENT { + log::error!( + "Failed to remove compiled hwdb database {:?}:{:?}", + hwdb_bin, +-- +2.33.0 + diff --git a/backport-refactor-devmaster-compress-unnecessary-code.patch b/backport-refactor-devmaster-compress-unnecessary-code.patch new file mode 100644 index 0000000..5ef0f7d --- /dev/null +++ b/backport-refactor-devmaster-compress-unnecessary-code.patch @@ -0,0 +1,68 @@ +From a01bde892736601aa57a2780e45b00cdb842dc16 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 16 Nov 2023 10:05:37 +0800 +Subject: [PATCH 065/103] refactor(devmaster): compress unnecessary code + +Eliminate unnecessary code for error handling. +--- + .../src/lib/framework/uevent_monitor.rs | 32 ++++--------------- + 1 file changed, 6 insertions(+), 26 deletions(-) + +diff --git a/exts/devmaster/src/lib/framework/uevent_monitor.rs b/exts/devmaster/src/lib/framework/uevent_monitor.rs +index 809071d1..30bbd570 100644 +--- a/exts/devmaster/src/lib/framework/uevent_monitor.rs ++++ b/exts/devmaster/src/lib/framework/uevent_monitor.rs +@@ -12,12 +12,9 @@ + + //! uevent_monitor + //! +-use crate::error::*; + use crate::framework::job_queue::JobQueue; + use device::device_monitor::{DeviceMonitor, MonitorNetlinkGroup}; + use event::{EventType, Events, Source}; +-use nix::errno::Errno; +-use snafu::ResultExt; + use std::os::unix::io::RawFd; + use std::rc::Rc; + +@@ -71,31 +68,14 @@ impl Source for UeventMonitor { + fn dispatch(&self, _: &Events) -> i32 { + let device = match self.device_monitor.receive_device() { + Ok(ret) => ret, +- Err(e) => match e { +- device::error::Error::Nix { +- msg: _, +- source: Errno::EAGAIN, +- } => { +- return 0; +- } +- device::error::Error::Nix { msg: _, source: _ } => { +- log::error!("{}", e); +- return 0; +- } +- _ => { +- return 0; +- } +- }, ++ Err(e) => { ++ log::error!("Monitor Error: {}", e); ++ return 0; ++ } + }; + +- log::debug!( +- "Monitor: received device {}", +- device +- .get_devpath() +- .context(DeviceSnafu) +- .log_error("uevent has no devpath") +- .unwrap_or_default() +- ); ++ /* The devpath is guaranteed to be valid. */ ++ log::debug!("Monitor: received device {}", device.get_devpath().unwrap()); + + self.job_queue.job_queue_insert(device); + 0 +-- +2.33.0 + diff --git a/backport-refactor-devmaster-rename-the-create_tmp_rules-funct.patch b/backport-refactor-devmaster-rename-the-create_tmp_rules-funct.patch new file mode 100644 index 0000000..be29b07 --- /dev/null +++ b/backport-refactor-devmaster-rename-the-create_tmp_rules-funct.patch @@ -0,0 +1,73 @@ +From 2fa2b67a81b6bbb1af175703bf00ebf990bac026 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 21 Nov 2023 20:02:49 +0800 +Subject: [PATCH 077/103] refactor(devmaster): rename the create_tmp_rules + function to create_tmp_file + +The function is used to create a temporary file and write content inside it. +It is more proper to have a more general name. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index 999c9692..b035f562 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -1724,7 +1724,7 @@ impl RuleToken { + } + + #[cfg(test)] +-mod tests { ++pub(crate) mod tests { + use basic::fs_util::touch_file; + use log::init_log; + use log::Level; +@@ -1736,7 +1736,7 @@ mod tests { + use std::panic::catch_unwind; + use std::{fs, path::Path}; + +- fn create_tmp_rules(dir: &'static str, file: &str, content: &str, truncate: bool) { ++ pub(crate) fn create_tmp_file(dir: &'static str, file: &str, content: &str, truncate: bool) { + assert!(fs::create_dir_all(dir).is_ok()); + let s = format!("{}/{}", dir, file); + let p = Path::new(&s); +@@ -1821,7 +1821,7 @@ mod tests { + ]; + + for &content in legal_rule.iter() { +- create_tmp_rules( ++ create_tmp_file( + "/tmp/devmaster/test_load_rules", + "00-test.rules", + content, +@@ -1954,7 +1954,7 @@ mod tests { + ]; + + for content in illegal_rule.iter() { +- create_tmp_rules( ++ create_tmp_file( + "/tmp/devmaster/test_load_rules_panic", + "00-test.rules", + content, +@@ -1990,7 +1990,7 @@ mod tests { + let illegal = vec!["OWNER=\"xxxx\"", "GROUP=\"xxxx\""]; + + for &content in legal.iter() { +- create_tmp_rules( ++ create_tmp_file( + "/tmp/devmaster/test_resolve_name_time", + "00-test.rules", + content, +@@ -2004,7 +2004,7 @@ mod tests { + } + + for &content in illegal.iter() { +- create_tmp_rules( ++ create_tmp_file( + "/tmp/devmaster/test_resolve_name_time", + "00-test.rules", + content, +-- +2.33.0 + diff --git a/backport-test-device-add-UT-for-device-enumerator.patch b/backport-test-device-add-UT-for-device-enumerator.patch new file mode 100644 index 0000000..406b18f --- /dev/null +++ b/backport-test-device-add-UT-for-device-enumerator.patch @@ -0,0 +1,1111 @@ +From 83cae246941e5b2c95d5c9a12746983a18f6c157 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 15 Nov 2023 15:29:02 +0800 +Subject: [PATCH 061/103] test(device): add UT for device enumerator + +--- + libs/device/src/device_enumerator.rs | 720 ++++++++++++++------------- + libs/device/src/error.rs | 6 +- + 2 files changed, 365 insertions(+), 361 deletions(-) + +diff --git a/libs/device/src/device_enumerator.rs b/libs/device/src/device_enumerator.rs +index 55f8681d..29391e0b 100644 +--- a/libs/device/src/device_enumerator.rs ++++ b/libs/device/src/device_enumerator.rs +@@ -12,10 +12,11 @@ + + //! enumerate /sys to collect devices + //! +-use crate::{device::Device, error::Error, utils::*}; ++use crate::{device::Device, error::*, utils::*, TAGS_BASE_DIR}; + use bitflags::bitflags; + use fnmatch_sys::fnmatch; + use nix::errno::Errno; ++use snafu::ResultExt; + use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, +@@ -45,7 +46,6 @@ impl Default for MatchInitializedType { + } + + /// enumerate devices or subsystems under /sys +-#[derive(Default)] + pub struct DeviceEnumerator { + /// enumerator type + pub(crate) etype: RefCell, +@@ -91,6 +91,40 @@ pub struct DeviceEnumerator { + + /// how to match device + pub(crate) match_initialized: RefCell, ++ ++ /// the base directory path to contain runtime temporary files of device database, tags, etc. ++ pub(crate) base_path: RefCell, ++} ++ ++impl Default for DeviceEnumerator { ++ fn default() -> Self { ++ Self::new() ++ } ++} ++ ++impl DeviceEnumerator { ++ /// create a default instance of DeviceEnumerator ++ pub fn new() -> Self { ++ Self { ++ etype: RefCell::new(DeviceEnumerationType::All), ++ devices_by_syspath: RefCell::new(HashMap::new()), ++ devices: RefCell::new(Vec::new()), ++ scan_up_to_date: RefCell::new(false), ++ sorted: RefCell::new(false), ++ prioritized_subsystems: RefCell::new(Vec::new()), ++ match_subsystem: RefCell::new(HashSet::new()), ++ not_match_subsystem: RefCell::new(HashSet::new()), ++ match_sysattr: RefCell::new(HashMap::new()), ++ not_match_sysattr: RefCell::new(HashMap::new()), ++ match_property: RefCell::new(HashMap::new()), ++ match_sysname: RefCell::new(HashSet::new()), ++ not_match_sysname: RefCell::new(HashSet::new()), ++ match_tag: RefCell::new(HashSet::new()), ++ match_parent: RefCell::new(HashSet::new()), ++ match_initialized: RefCell::new(MatchInitializedType::ALL), ++ base_path: RefCell::new(crate::DEFAULT_BASE_DIR.to_string()), ++ } ++ } + } + + /// decide enumerate devices or subsystems +@@ -186,11 +220,6 @@ bitflags! { + + /// public methods + impl DeviceEnumerator { +- /// create a default instance of DeviceEnumerator +- pub fn new() -> DeviceEnumerator { +- DeviceEnumerator::default() +- } +- + /// set the enumerator type + pub fn set_enumerator_type(&mut self, etype: DeviceEnumerationType) { + if *self.etype.borrow() != etype { +@@ -426,23 +455,11 @@ impl DeviceEnumerator { + + for (property_pattern, value_pattern) in self.match_property.borrow().iter() { + for (property, value) in &device.property_iter() { +- if !self +- .pattern_match(property_pattern, property) +- .map_err(|e| Error::Nix { +- msg: format!("match_property failed: {}", e), +- source: e.get_errno(), +- })? +- { ++ if !self.pattern_match(property_pattern, property) { + continue; + } + +- if self +- .pattern_match(value_pattern, value) +- .map_err(|e| Error::Nix { +- msg: format!("match_property failed: {}", e), +- source: e.get_errno(), +- })? +- { ++ if self.pattern_match(value_pattern, value) { + return Ok(true); + } + } +@@ -458,7 +475,7 @@ impl DeviceEnumerator { + } + + /// check whether the sysname of a device matches +- pub(crate) fn match_sysname(&self, sysname: &str) -> Result { ++ pub(crate) fn match_sysname(&self, sysname: &str) -> bool { + self.set_pattern_match( + &self.match_sysname.borrow(), + &self.not_match_sysname.borrow(), +@@ -473,7 +490,7 @@ impl DeviceEnumerator { + } + + /// check whether the subsystem of a device matches +- pub(crate) fn match_subsystem(&self, subsystem: &str) -> Result { ++ pub(crate) fn match_subsystem(&self, subsystem: &str) -> bool { + self.set_pattern_match( + &self.match_subsystem.borrow(), + &self.not_match_subsystem.borrow(), +@@ -529,7 +546,7 @@ impl DeviceEnumerator { + return Ok(true); + } + +- self.pattern_match(patterns, &value) ++ Ok(self.pattern_match(patterns, &value)) + } + + /// check whether a device matches conditions according to flags +@@ -538,19 +555,8 @@ impl DeviceEnumerator { + device: &mut Device, + flags: MatchFlag, + ) -> Result { +- if (flags & MatchFlag::SYSNAME).bits() != 0 { +- match self.match_sysname(&device.get_sysname()?) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match sysname failed: {}", e), +- source: e.get_errno(), +- }) +- } +- } ++ if (flags & MatchFlag::SYSNAME).bits() != 0 && !self.match_sysname(&device.get_sysname()?) { ++ return Ok(false); + } + + if (flags & MatchFlag::SUBSYSTEM).bits() != 0 { +@@ -568,87 +574,29 @@ impl DeviceEnumerator { + } + }; + +- match self.match_subsystem(&subsystem) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match_subsystem ({})", e), +- source: e.get_errno(), +- }); +- } ++ if !self.match_subsystem(&subsystem) { ++ return Ok(false); + } + } + +- if (flags & MatchFlag::PARENT).bits() != 0 { +- match self.match_parent(device) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match parent failed: {}", e), +- source: e.get_errno(), +- }); +- } +- } ++ if (flags & MatchFlag::PARENT).bits() != 0 && !self.match_parent(device)? { ++ return Ok(false); + } + +- if (flags & MatchFlag::TAG).bits() != 0 { +- match self.match_tag(device) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match tag failed: {}", e), +- source: e.get_errno(), +- }); +- } +- } ++ if (flags & MatchFlag::TAG).bits() != 0 && !self.match_tag(device)? { ++ return Ok(false); + } + +- match self.match_initialized(device) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match_initialized ({})", e), +- source: e.get_errno(), +- }); +- } ++ if !self.match_initialized(device)? { ++ return Ok(false); + } + +- match self.match_property(device) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match property failed: {}", e), +- source: e.get_errno(), +- }); +- } ++ if !self.match_property(device)? { ++ return Ok(false); + } + +- match self.match_sysattr(device) { +- Ok(ret) => match ret { +- true => {} +- false => return Ok(false), +- }, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("test_matches failed: match sysattr failed: {}", e), +- source: e.get_errno(), +- }); +- } ++ if !self.match_sysattr(device)? { ++ return Ok(false); + } + + Ok(true) +@@ -679,20 +627,11 @@ impl DeviceEnumerator { + + d = parent.clone(); + +- if !self +- .test_matches(&mut parent.borrow_mut(), flags) +- .map_err(|e| Error::Nix { +- msg: format!("add_parent_devices failed: {}", e), +- source: e.get_errno(), +- })? +- { ++ if !self.test_matches(&mut parent.borrow_mut(), flags)? { + continue; + } + +- if !self.add_device(parent.clone()).map_err(|e| Error::Nix { +- msg: format!("add_parent_devices failed: {}", e), +- source: e.get_errno(), +- })? { ++ if !self.add_device(parent.clone())? { + break; + } + } +@@ -711,12 +650,8 @@ impl DeviceEnumerator { + let mut path: Vec = vec!["/sys".to_string(), basedir]; + path.append(&mut subdirs); + let path = path.join("/"); +- let path = match Path::new(&path).canonicalize().map_err(|e| Error::Nix { +- msg: format!( +- "scan_dir_and_add_devices failed: canonicalize {} ({})", +- path, e +- ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), ++ let path = match Path::new(&path).canonicalize().context(Io { ++ msg: format!("failed to canonicalize '{}'", path), + }) { + Ok(p) => p, + Err(e) => { +@@ -729,37 +664,31 @@ impl DeviceEnumerator { + + let entries = std::fs::read_dir(path).unwrap(); + for entry in entries { +- if let Err(e) = entry { +- ret = Err(Error::Nix { +- msg: format!("scan_dir_and_add_devices failed: read entries ({})", e), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), +- }); +- continue; +- } +- +- let entry = entry.unwrap(); ++ let entry = match entry.context(Io { ++ msg: "failed to read directory entry".to_string(), ++ }) { ++ Ok(i) => i, ++ Err(e) => { ++ ret = Err(e); ++ continue; ++ } ++ }; + + if !relevant_sysfs_subdir(&entry) { + continue; + } + +- if !self.match_sysname(entry.file_name().to_str().unwrap_or_default())? { ++ if !self.match_sysname(entry.file_name().to_str().unwrap_or_default()) { + continue; + } + +- let syspath = match entry.path().canonicalize() { ++ let syspath = match entry.path().canonicalize().context(Io { ++ msg: format!("failed to canonicalize '{:?}'", entry.path()), ++ }) { + Ok(ret) => ret, + Err(e) => { +- if let Some(errno) = e.raw_os_error() { +- if errno != libc::ENODEV { +- ret = Err(Error::Nix { +- msg: format!( +- "scan_dir_and_add_devices failed: can't canonicalize '{:?}': {}", +- entry, e +- ), +- source: Errno::from_i32(errno), +- }); +- } ++ if e.get_errno() != Errno::ENODEV { ++ ret = Err(e); + } + continue; + } +@@ -787,10 +716,7 @@ impl DeviceEnumerator { + continue; + } + Err(e) => { +- ret = Err(Error::Nix { +- msg: format!("scan_dir_and_add_devices failed: {}", e), +- source: e.get_errno(), +- }); ++ ret = Err(e); + continue; + } + }; +@@ -806,15 +732,11 @@ impl DeviceEnumerator { + }; + + // also include all potentially matching parent devices. +- match self.add_parent_devices(device.clone(), MatchFlag::ALL) { +- Ok(_) => {} +- Err(e) => { +- ret = Err(Error::Nix { +- msg: format!("scan_dir_and_add_devices failed: {}", e), +- source: e.get_errno(), +- }); +- } +- }; ++ let _ = self ++ .add_parent_devices(device.clone(), MatchFlag::ALL) ++ .map_err(|e| { ++ ret = Err(e); ++ }); + } + + ret +@@ -828,15 +750,9 @@ impl DeviceEnumerator { + subsystem: Option, + ) -> Result<(), Error> { + let path_str = "/sys/".to_string() + basedir.as_str(); +- let path = match Path::new(&path_str).canonicalize() { +- Ok(ret) => ret, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("scan_dir failed: can't canonicalize '{}': {}", basedir, e), +- source: Errno::EINVAL, +- }); +- } +- }; ++ let path = Path::new(&path_str).canonicalize().context(Io { ++ msg: format!("fail to canonicalize '{}'", path_str), ++ })?; + + let dir = std::fs::read_dir(path); + if let Err(e) = dir { +@@ -853,16 +769,12 @@ impl DeviceEnumerator { + let mut ret = Result::<(), Error>::Ok(()); + + for entry in dir.unwrap() { +- let entry = match entry { ++ let entry = match entry.context(Io { ++ msg: format!("failed to read entry under '{}'", path_str), ++ }) { + Ok(e) => e, + Err(e) => { +- ret = Err(Error::Nix { +- msg: format!( +- "scan_dir failed: can't read entries from directory '{}'", +- path_str +- ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), +- }); ++ ret = Err(e); + continue; + } + }; +@@ -871,14 +783,13 @@ impl DeviceEnumerator { + continue; + } + +- match self.match_subsystem( ++ if !self.match_subsystem( + subsystem + .clone() + .unwrap_or_else(|| entry.file_name().to_str().unwrap_or_default().to_string()) + .as_str(), + ) { +- Ok(false) | Err(_) => continue, +- Ok(true) => {} ++ continue; + } + + let mut subdirs = vec![entry.file_name().to_str().unwrap_or_default().to_string()]; +@@ -887,30 +798,30 @@ impl DeviceEnumerator { + subdirs.push(subdir.clone().unwrap()); + } + +- if let Err(e) = self.scan_dir_and_add_devices(basedir.clone(), subdirs) { +- ret = Err(Error::Nix { +- msg: format!("scan_dir failed: {}", e), +- source: e.get_errno(), ++ let _ = self ++ .scan_dir_and_add_devices(basedir.clone(), subdirs) ++ .map_err(|e| { ++ ret = Err(e); + }); +- } + } + ret + } + + /// scan devices for a single tag + 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 { ++ let path = Path::new(self.base_path.borrow().as_str()) ++ .join(TAGS_BASE_DIR) ++ .join(tag); ++ let dir = std::fs::read_dir(&path); ++ let tag_dir = match dir.context(Io { ++ msg: format!("failed to read '{:?}'", path), ++ }) { + Ok(d) => d, + Err(e) => { +- if e.raw_os_error().unwrap_or_default() == libc::ENOENT { ++ if e.get_errno() == Errno::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()), +- }); ++ return Err(e); + } + } + }; +@@ -919,16 +830,12 @@ impl DeviceEnumerator { + + let mut ret = Ok(()); + for entry in tag_dir { +- let entry = match entry { ++ let entry = match entry.context(Io { ++ msg: format!("failed to read entry under '{:?}'", path), ++ }) { + 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()), +- }); ++ ret = Err(e); + continue; + } + }; +@@ -977,10 +884,7 @@ impl DeviceEnumerator { + + for tag in self.match_tag.borrow().iter() { + if let Err(e) = self.scan_devices_tag(tag) { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_tags failed: {}", e), +- source: e.get_errno(), +- }); ++ ret = Err(e); + } + } + ret +@@ -1012,40 +916,31 @@ impl DeviceEnumerator { + path: &str, + stack: &mut HashSet, + ) -> Result<(), Error> { +- let entries = match std::fs::read_dir(path) { ++ let entries = match std::fs::read_dir(path).context(Io { ++ msg: format!("failed to read '{}'", path), ++ }) { + Ok(ret) => ret, + Err(e) => { +- let errno = e.raw_os_error().unwrap_or_default(); +- if errno == libc::ENOENT { ++ if e.get_errno() == Errno::ENOENT { + return Ok(()); + } else { +- return Err(Error::Nix { +- msg: format!( +- "parent_crawl_children failed: can't read directory '{}'", +- path +- ), +- source: Errno::from_i32(errno), +- }); ++ return Err(e); + } + } + }; + let mut ret = Result::<(), Error>::Ok(()); + for entry in entries { +- let entry = match entry { ++ let entry = match entry.context(Io { ++ msg: format!("failed to read entry under'{:?}'", path), ++ }) { + Ok(e) => e, + Err(e) => { +- ret = Err(Error::Nix { +- msg: format!( +- "parent_crawl_children failed: can't read entries under '{}'", +- path +- ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), +- }); ++ ret = Err(e); + continue; + } + }; +- let file_name = entry.file_name(); +- let file_name = file_name.to_str().unwrap_or_default(); ++ ++ let file_name = entry.file_name().to_str().unwrap_or_default().to_string(); + if file_name.is_empty() || file_name.starts_with('.') { + continue; + } +@@ -1055,29 +950,22 @@ impl DeviceEnumerator { + continue; + } + +- let entry_path = match entry.path().canonicalize() { ++ let entry_path = match entry.path().canonicalize().context(Io { ++ msg: format!("fail to canonicalize '{:?}'", entry.path()), ++ }) { + Ok(p) => p.to_str().unwrap_or_default().to_string(), + Err(e) => { +- ret = Err(Error::Nix { +- msg: format!( +- "parent_crawl_children failed: can't canonicalize '{:?}'", +- entry +- ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), +- }); ++ ret = Err(e); + continue; + } + }; + +- if let Ok(true) = self.match_sysname(file_name) { ++ if self.match_sysname(&file_name) { + if let Err(e) = self.parent_add_child( + &entry_path, + MatchFlag::ALL & !(MatchFlag::SYSNAME | MatchFlag::PARENT), + ) { +- ret = Err(Error::Nix { +- msg: format!("parent_crawl_children failed: {}", e), +- source: e.get_errno(), +- }); ++ ret = Err(e); + }; + } + +@@ -1099,17 +987,11 @@ impl DeviceEnumerator { + .parent_add_child(path, MatchFlag::ALL & !MatchFlag::PARENT) + .map(|_| ()) + { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_children failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e) + } + + if let Err(e) = self.parent_crawl_children(path, &mut stack) { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_children failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e) + } + } + +@@ -1117,10 +999,7 @@ impl DeviceEnumerator { + stack.remove(&path); + + if let Err(e) = self.parent_crawl_children(&path, &mut stack) { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_children failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e) + } + } + +@@ -1132,17 +1011,11 @@ impl DeviceEnumerator { + let mut ret = Result::<(), Error>::Ok(()); + + if let Err(e) = self.scan_dir("bus".to_string(), Some("devices".to_string()), None) { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_all failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e); + } + + if let Err(e) = self.scan_dir("class".to_string(), None, None) { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_all failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e); + } + + ret +@@ -1152,44 +1025,35 @@ impl DeviceEnumerator { + pub(crate) fn scan_subsystems_all(&mut self) -> Result<(), Error> { + let mut ret = Result::<(), Error>::Ok(()); + +- if self.match_subsystem("module").map_err(|e| Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- })? { +- if let Err(e) = self.scan_dir_and_add_devices("module".to_string(), vec![]) { +- ret = Err(Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ if self.match_subsystem("module") { ++ let _ = self ++ .scan_dir_and_add_devices("module".to_string(), vec![]) ++ .map_err(|e| { ++ ret = Err(e); ++ log::error!("Enumerator: failed to scan modules"); ++ }); + } + +- if self.match_subsystem("subsystem").map_err(|e| Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- })? { +- if let Err(e) = self.scan_dir_and_add_devices("bus".to_string(), vec![]) { +- ret = Err(Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ if self.match_subsystem("subsystem") { ++ let _ = self ++ .scan_dir_and_add_devices("bus".to_string(), vec![]) ++ .map_err(|e| { ++ ret = Err(e); ++ log::error!("Enumerator: failed to scan subsystems"); ++ }); + } + +- if self.match_subsystem("drivers").map_err(|e| Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- })? { +- if let Err(e) = self.scan_dir( +- "bus".to_string(), +- Some("drivers".to_string()), +- Some("drivers".to_string()), +- ) { +- ret = Err(Error::Nix { +- msg: format!("scan_subsystems_all failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ if self.match_subsystem("drivers") { ++ let _ = self ++ .scan_dir( ++ "bus".to_string(), ++ Some("drivers".to_string()), ++ Some("drivers".to_string()), ++ ) ++ .map_err(|e| { ++ ret = Err(e); ++ log::error!("Enumerator: failed to scan drivers"); ++ }); + } + + ret +@@ -1209,24 +1073,11 @@ impl DeviceEnumerator { + let mut ret = Result::<(), Error>::Ok(()); + + if !self.match_tag.borrow().is_empty() { +- if let Err(e) = self.scan_devices_tags() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ let _ = self.scan_devices_tags().map_err(|e| ret = Err(e)); + } else if !self.match_parent.borrow().is_empty() { +- if let Err(e) = self.scan_devices_children() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ let _ = self.scan_devices_children().map_err(|e| ret = Err(e)); + } else if let Err(e) = self.scan_devices_all() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices failed: {}", e), +- source: e.get_errno(), +- }) ++ ret = Err(e); + } + + self.scan_up_to_date.replace(true); +@@ -1247,10 +1098,7 @@ impl DeviceEnumerator { + self.devices_by_syspath.borrow_mut().clear(); + self.devices.borrow_mut().clear(); + +- let ret = self.scan_subsystems_all().map_err(|e| Error::Nix { +- msg: format!("scan_subsystems failed: {}", e), +- source: e.get_errno(), +- }); ++ let ret = self.scan_subsystems_all(); + + self.scan_up_to_date.replace(true); + self.etype.replace(DeviceEnumerationType::Subsystems); +@@ -1271,33 +1119,18 @@ impl DeviceEnumerator { + let mut ret = Result::<(), Error>::Ok(()); + + if !self.match_tag.borrow().is_empty() { +- if let Err(e) = self.scan_devices_tags() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_and_subsystems failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ ret = self.scan_devices_tags(); + } else if !self.match_parent.borrow().is_empty() { +- if let Err(e) = self.scan_devices_children() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_and_subsystems failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ ret = self.scan_devices_children(); + } else { +- if let Err(e) = self.scan_devices_all() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_and_subsystems failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ let _ = self.scan_devices_all().map_err(|e| { ++ ret = Err(e); ++ log::error!("Failed to scan devices."); ++ }); + +- if let Err(e) = self.scan_subsystems_all() { +- ret = Err(Error::Nix { +- msg: format!("scan_devices_and_subsystems failed: {}", e), +- source: e.get_errno(), +- }) +- } ++ let _ = self.scan_subsystems_all().map_err(|e| { ++ ret = Err(e); ++ }); + } + + self.scan_up_to_date.replace(true); +@@ -1306,20 +1139,18 @@ impl DeviceEnumerator { + ret + } + +- /// pattern match +- /// if the enumerator filter is of Glob type, use unix glob-fnmatch to check whether match +- /// if the enumerator filter is of Regular type, use typical regular expression to check whether match +- pub(crate) fn pattern_match(&self, pattern: &str, value: &str) -> Result { ++ /// Pattern match based on glob style pattern ++ pub(crate) fn pattern_match(&self, pattern: &str, value: &str) -> bool { + let pattern = format!("{}\0", pattern); + let value = format!("{}\0", value); + +- Ok(unsafe { ++ unsafe { + fnmatch( + pattern.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + 0, +- ) +- } == 0) ++ ) == 0 ++ } + } + + /// if any exclude pattern matches, return false +@@ -1330,38 +1161,32 @@ impl DeviceEnumerator { + include_pattern_set: &HashSet, + exclude_pattern_set: &HashSet, + value: &str, +- ) -> Result { ++ ) -> bool { + for pattern in exclude_pattern_set.iter() { +- if self.pattern_match(pattern, value).map_err(|e| Error::Nix { +- msg: format!("set_pattern_match failed: pattern_match exclude ({})", e), +- source: e.get_errno(), +- })? { +- return Ok(false); ++ if self.pattern_match(pattern, value) { ++ return false; + } + } + + if include_pattern_set.is_empty() { +- return Ok(true); ++ return true; + } + + for pattern in include_pattern_set.iter() { +- if self.pattern_match(pattern, value).map_err(|e| Error::Nix { +- msg: format!("set_pattern_match failed: pattern_match include ({})", e), +- source: e.get_errno(), +- })? { +- return Ok(true); ++ if self.pattern_match(pattern, value) { ++ return true; + } + } + +- Ok(false) ++ false + } + } + + #[cfg(test)] + mod tests { +- use crate::device_enumerator::DeviceEnumerationType; ++ use crate::{device_enumerator::DeviceEnumerationType, Device}; + +- use super::DeviceEnumerator; ++ use super::{DeviceEnumerator, MatchInitializedType}; + + #[test] + fn test_enumerator_inialize() { +@@ -1374,6 +1199,17 @@ mod tests { + enumerator.add_match_property("DEVTYPE", "char").unwrap(); + enumerator.add_match_sysname("sda", true).unwrap(); + enumerator.add_match_sysname("sdb", false).unwrap(); ++ enumerator.add_prioritized_subsystem("net").unwrap(); ++ enumerator.add_match_tag("devmaster").unwrap(); ++ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ++ enumerator.add_match_parent_incremental(&dev).unwrap(); ++ enumerator.add_match_parent(&dev).unwrap(); ++ enumerator.allow_uninitialized().unwrap(); ++ enumerator ++ .add_match_is_initialized(MatchInitializedType::ALL) ++ .unwrap(); + } + + #[test] +@@ -1409,6 +1245,176 @@ mod tests { + #[test] + fn test_pattern_match() { + let enumerator = DeviceEnumerator::new(); +- assert!(enumerator.pattern_match("hello*", "hello world").unwrap()); ++ assert!(enumerator.pattern_match("hello*", "hello world")); ++ assert!(!enumerator.pattern_match("hello*", "world")); ++ } ++ ++ trait State { ++ fn trans(self: Box, s: &str) -> Box; ++ } ++ ++ struct StateMachine { ++ state: Option>, ++ } ++ ++ impl StateMachine { ++ fn trans(&mut self, s: &str) { ++ if let Some(state) = self.state.take() { ++ self.state = Some(state.trans(s)); ++ } ++ } ++ } ++ ++ struct Init; ++ ++ impl State for Init { ++ fn trans(self: Box, s: &str) -> Box { ++ match s { ++ "net" => Box::new(Front {}), ++ _ => panic!(), ++ } ++ } ++ } ++ ++ struct Front; ++ ++ impl State for Front { ++ fn trans(self: Box, s: &str) -> Box { ++ match s { ++ "net" => Box::new(Front {}), ++ _ => Box::new(Tail {}), ++ } ++ } ++ } ++ ++ struct Tail; ++ ++ impl State for Tail { ++ fn trans(self: Box, s: &str) -> Box { ++ match s { ++ "net" => panic!(), ++ _ => Box::new(Tail {}), ++ } ++ } ++ } ++ ++ #[test] ++ fn test_priority_subsystem() { ++ /* If the prioritized subsystem is set, e.g., "net", ++ * the "net" devices should be ordered in the front of the scanned devices. ++ */ ++ let mut ert = DeviceEnumerator::new(); ++ ert.set_enumerator_type(DeviceEnumerationType::Devices); ++ ert.add_prioritized_subsystem("net").unwrap(); ++ ert.add_prioritized_subsystem("block").unwrap(); ++ ++ let mut sm = StateMachine { ++ state: Some(Box::new(Init {})), ++ }; ++ ++ for dev in ert.iter() { ++ sm.trans(&dev.borrow().get_subsystem().unwrap()); ++ } ++ ++ ert.sort_devices().unwrap(); ++ } ++ ++ #[test] ++ fn test_match_property() { ++ let mut ert = DeviceEnumerator::new(); ++ ++ let mut dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ++ dev.add_property("helloxxx", "worldxxx").unwrap(); ++ ++ ert.add_match_property("hello*", "world*").unwrap(); ++ assert!(ert.match_property(&mut dev).unwrap()); ++ } ++ ++ #[test] ++ fn test_match_subsystem() { ++ let mut ert = DeviceEnumerator::new(); ++ ++ ert.add_match_subsystem("net", true).unwrap(); ++ ert.add_match_subsystem("block", false).unwrap(); ++ ++ assert!(ert.match_subsystem("net")); ++ assert!(!ert.match_subsystem("block")); ++ } ++ ++ #[test] ++ fn test_match_sysattr() { ++ let mut ert = DeviceEnumerator::new(); ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ++ ert.add_match_sysattr("ifindex", "1", true).unwrap(); ++ ert.add_match_sysattr("address", "aa:aa:aa:aa:aa:aa", false) ++ .unwrap(); ++ ++ assert!(ert.match_sysattr(&dev).unwrap()); ++ } ++ ++ #[test] ++ fn test_match_sysname() { ++ let mut ert = DeviceEnumerator::new(); ++ ert.add_match_sysname("loop*", true).unwrap(); ++ ert.add_match_sysname("sd*", false).unwrap(); ++ assert!(ert.match_sysname("loop1")); ++ assert!(!ert.match_sysname("sda")); ++ } ++ ++ #[test] ++ fn test_match_tag() { ++ let mut ert = DeviceEnumerator::new(); ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ dev.set_base_path("/tmp/devmaster"); ++ dev.add_tag("devmaster", true); ++ dev.update_tag("devmaster", true).unwrap(); ++ ert.add_match_tag("devmaster").unwrap(); ++ assert!(ert.match_tag(&dev).unwrap()); ++ ++ ert.set_enumerator_type(DeviceEnumerationType::Devices); ++ ert.scan_devices().unwrap(); ++ ++ dev.update_tag("devmaster", false).unwrap(); ++ } ++ ++ #[test] ++ fn test_match_parent_incremental() { ++ let mut ert = DeviceEnumerator::new(); ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ assert!(ert.match_parent(&dev).unwrap()); ++ ert.add_match_parent_incremental(&dev).unwrap(); ++ assert!(ert.match_parent(&dev).unwrap()); ++ let dev_1 = Device::from_subsystem_sysname("drivers", "usb:usb").unwrap(); ++ assert!(!ert.match_parent(&dev_1).unwrap()); ++ ++ ert.set_enumerator_type(DeviceEnumerationType::Devices); ++ ert.scan_devices().unwrap(); ++ } ++ ++ #[test] ++ fn test_match_parent() { ++ let mut ert = DeviceEnumerator::new(); ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ert.add_match_parent_incremental(&dev).unwrap(); ++ assert!(ert.match_parent(&dev).unwrap()); ++ } ++ ++ #[test] ++ fn test_match_is_initialized() { ++ let mut ert = DeviceEnumerator::new(); ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ert.add_match_is_initialized(MatchInitializedType::ALL) ++ .unwrap(); ++ assert!(ert.match_initialized(&dev).unwrap()); ++ } ++ ++ #[test] ++ fn test_scan_subsystems_all() { ++ let mut ert = DeviceEnumerator::new(); ++ ert.add_match_subsystem("module", true).unwrap(); ++ ert.add_match_subsystem("subsystem", true).unwrap(); ++ ert.add_match_subsystem("drivers", true).unwrap(); + } + } +diff --git a/libs/device/src/error.rs b/libs/device/src/error.rs +index 2cea6f4b..3d7307b7 100644 +--- a/libs/device/src/error.rs ++++ b/libs/device/src/error.rs +@@ -12,6 +12,7 @@ + + //! Error definition of device + //! ++use basic::IN_SET; + use nix::errno::Errno; + use snafu::prelude::Snafu; + +@@ -77,10 +78,7 @@ impl Error { + + /// check whether the device error indicates the device is absent + pub fn is_absent(&self) -> bool { +- matches!( +- self.get_errno(), +- Errno::ENODEV | Errno::ENXIO | Errno::ENOENT +- ) ++ IN_SET!(self.get_errno(), Errno::ENODEV, Errno::ENXIO, Errno::ENOENT) + } + + pub(crate) fn replace_errno(self, from: Errno, to: Errno) -> Self { +-- +2.33.0 + diff --git a/backport-test-device-add-UT-for-device-monitor.patch b/backport-test-device-add-UT-for-device-monitor.patch new file mode 100644 index 0000000..98e0924 --- /dev/null +++ b/backport-test-device-add-UT-for-device-monitor.patch @@ -0,0 +1,75 @@ +From c600ca109d74c38f130b84353b6f21ff2439a36d Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 15 Nov 2023 15:35:26 +0800 +Subject: [PATCH 062/103] test(device): add UT for device monitor + +--- + libs/device/src/device_monitor.rs | 19 +++++++++++++------ + 1 file changed, 13 insertions(+), 6 deletions(-) + +diff --git a/libs/device/src/device_monitor.rs b/libs/device/src/device_monitor.rs +index 3f1d1ba6..c6ad3ec6 100644 +--- a/libs/device/src/device_monitor.rs ++++ b/libs/device/src/device_monitor.rs +@@ -216,7 +216,7 @@ impl Drop for DeviceMonitor { + #[cfg(test)] + mod tests { + use super::*; +- use crate::device::*; ++ use crate::{device::*, DeviceAction}; + use event::*; + use std::{os::unix::prelude::RawFd, rc::Rc, thread::spawn}; + +@@ -264,9 +264,13 @@ mod tests { + } + + /// test whether device monitor can receive uevent from kernel normally +- #[ignore] + #[test] + fn test_monitor_kernel() { ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ if device.trigger(DeviceAction::Change).is_err() { ++ return; ++ } ++ + let e = Events::new().unwrap(); + let s: Rc = Rc::new(Monitor { + device_monitor: DeviceMonitor::new(MonitorNetlinkGroup::Kernel, None), +@@ -275,7 +279,7 @@ mod tests { + e.set_enabled(s.clone(), EventState::On).unwrap(); + + spawn(|| { +- let device = Device::from_devname("/dev/sda").unwrap(); ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); + device.set_sysattr_value("uevent", Some("change")).unwrap(); + }) + .join() +@@ -287,9 +291,13 @@ mod tests { + } + + /// test whether device monitor can receive device message from userspace normally +- #[ignore] + #[test] + fn test_monitor_userspace() { ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ if device.trigger(DeviceAction::Change).is_err() { ++ return; ++ } ++ + let e = Events::new().unwrap(); + let s: Rc = Rc::new(Monitor { + device_monitor: DeviceMonitor::new(MonitorNetlinkGroup::Userspace, None), +@@ -298,9 +306,8 @@ mod tests { + e.set_enabled(s.clone(), EventState::On).unwrap(); + + spawn(|| { +- let device = Device::from_devname("/dev/sda").unwrap(); ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); + device.set_action_from_string("change").unwrap(); +- device.set_subsystem("block"); + device.set_seqnum(1000); + + let broadcaster = DeviceMonitor::new(MonitorNetlinkGroup::None, None); +-- +2.33.0 + diff --git a/backport-test-device-compress-closures-and-add-some-unit-case.patch b/backport-test-device-compress-closures-and-add-some-unit-case.patch new file mode 100644 index 0000000..bec37aa --- /dev/null +++ b/backport-test-device-compress-closures-and-add-some-unit-case.patch @@ -0,0 +1,1565 @@ +From 0bae5d9fad3196c79f88a9d47eba38e89d5f291f Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 05:18:36 +0800 +Subject: [PATCH 049/103] test(device): compress closures and add some unit + cases + +Some unnecessary closures may decrease line coverage, thus compress +them. + +Also drop Clone trait implementation for Device as it is not necessary. +--- + .../src/bin/devctl/subcmds/devctl_info.rs | 4 +- + exts/devmaster/src/lib/builtin/hwdb.rs | 12 +- + exts/devmaster/src/lib/framework/devmaster.rs | 1 - + exts/devmaster/src/lib/framework/job_queue.rs | 2 - + .../src/lib/framework/uevent_monitor.rs | 1 - + libs/device/examples/device_from.rs | 4 +- + libs/device/src/device.rs | 689 +++++++----------- + libs/device/src/device_enumerator.rs | 10 +- + libs/device/src/device_monitor.rs | 2 +- + libs/device/src/error.rs | 65 +- + 10 files changed, 324 insertions(+), 466 deletions(-) + +diff --git a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs +index 71c9254d..d4fcb6c4 100644 +--- a/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs ++++ b/exts/devmaster/src/bin/devctl/subcmds/devctl_info.rs +@@ -286,7 +286,7 @@ fn print_device_chain(device: Device) -> Result<()> { + while let Ok(parent) = child.get_parent() { + print_all_attributes(&parent.borrow(), true)?; + +- child = parent.borrow().clone(); ++ child = parent.borrow().shallow_clone().unwrap(); + } + + Ok(()) +@@ -489,7 +489,7 @@ fn export_devices() -> Result<()> { + } + + for device in e.iter() { +- print_record(device.borrow().clone(), ""); ++ print_record(device.borrow().shallow_clone().unwrap(), ""); + } + + Ok(()) +diff --git a/exts/devmaster/src/lib/builtin/hwdb.rs b/exts/devmaster/src/lib/builtin/hwdb.rs +index 63308e37..0b797874 100644 +--- a/exts/devmaster/src/lib/builtin/hwdb.rs ++++ b/exts/devmaster/src/lib/builtin/hwdb.rs +@@ -123,7 +123,7 @@ impl Hwdb { + let mut last = false; + let mut src_dev = match srcdev { + Some(d) => d, +- None => dev.borrow_mut().clone(), ++ None => dev.borrow_mut().shallow_clone().unwrap(), + }; + + loop { +@@ -131,7 +131,7 @@ impl Hwdb { + Ok(str_subsystem) => str_subsystem, + Err(_) => { + src_dev = match src_dev.get_parent() { +- Ok(d) => d.borrow_mut().clone(), ++ Ok(d) => d.borrow_mut().shallow_clone().unwrap(), + Err(_) => break, + }; + continue; +@@ -142,7 +142,7 @@ impl Hwdb { + if let Some(str_subsystem) = subsystem { + if &dsubsys != str_subsystem { + src_dev = match src_dev.get_parent() { +- Ok(d) => d.borrow_mut().clone(), ++ Ok(d) => d.borrow_mut().shallow_clone().unwrap(), + Err(_) => break, + }; + continue; +@@ -170,7 +170,7 @@ impl Hwdb { + + if modalias.is_empty() { + src_dev = match src_dev.get_parent() { +- Ok(d) => d.borrow_mut().clone(), ++ Ok(d) => d.borrow_mut().shallow_clone().unwrap(), + Err(_) => break, + }; + continue; +@@ -189,7 +189,7 @@ impl Hwdb { + } + + src_dev = match src_dev.get_parent() { +- Ok(d) => d.borrow_mut().clone(), ++ Ok(d) => d.borrow_mut().shallow_clone().unwrap(), + Err(_) => break, + }; + } +@@ -258,7 +258,7 @@ impl Builtin for Hwdb { + Ok(srcdev) => Some(srcdev), + Err(e) => { + return Err(Error::Other { +- msg: format!("Failed to create sd_device object '{:?}'", dev), ++ msg: format!("Failed to create device object '{}'", device_id), + errno: e.get_errno(), + }); + } +diff --git a/exts/devmaster/src/lib/framework/devmaster.rs b/exts/devmaster/src/lib/framework/devmaster.rs +index 370f4e84..94b93ca0 100644 +--- a/exts/devmaster/src/lib/framework/devmaster.rs ++++ b/exts/devmaster/src/lib/framework/devmaster.rs +@@ -24,7 +24,6 @@ use std::{ + }; + + /// encapsulate all submanagers +-#[derive(Debug)] + pub struct Devmaster { + /// reference to events + pub(crate) events: Rc, +diff --git a/exts/devmaster/src/lib/framework/job_queue.rs b/exts/devmaster/src/lib/framework/job_queue.rs +index e3e6e90b..7c6d6a8d 100644 +--- a/exts/devmaster/src/lib/framework/job_queue.rs ++++ b/exts/devmaster/src/lib/framework/job_queue.rs +@@ -51,7 +51,6 @@ impl Display for JobState { + } + + /// device job +-#[derive(Debug)] + pub struct DeviceJob { + /// internal device + pub device: Device, +@@ -132,7 +131,6 @@ impl PartialEq for DeviceJob { + } + + /// job queue +-#[derive(Debug)] + pub struct JobQueue { + /// internal container of jobs + pub(crate) jobs: RefCell>>, +diff --git a/exts/devmaster/src/lib/framework/uevent_monitor.rs b/exts/devmaster/src/lib/framework/uevent_monitor.rs +index fe7d19c3..809071d1 100644 +--- a/exts/devmaster/src/lib/framework/uevent_monitor.rs ++++ b/exts/devmaster/src/lib/framework/uevent_monitor.rs +@@ -22,7 +22,6 @@ use std::os::unix::io::RawFd; + use std::rc::Rc; + + /// uevent monitor +-#[derive(Debug)] + pub struct UeventMonitor { + /// receive uevent from netlink socket + device_monitor: DeviceMonitor, +diff --git a/libs/device/examples/device_from.rs b/libs/device/examples/device_from.rs +index 29e24bbc..146543ee 100644 +--- a/libs/device/examples/device_from.rs ++++ b/libs/device/examples/device_from.rs +@@ -30,11 +30,11 @@ fn main() { + let dev = Device::from_subsystem_sysname("drivers", "usb:hub").unwrap(); + println!("{}", dev.get_sysname().unwrap()); + println!("{}", dev.get_subsystem().unwrap()); +- println!("{:?}", dev); ++ println!("{}", dev.get_device_id().unwrap()); + } + + { + let dev = Device::from_ifindex(2).unwrap(); +- println!("{:?}", dev); ++ println!("{}", dev.get_device_id().unwrap()); + } + } +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index 6673d823..284cd6e9 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -12,7 +12,6 @@ + + //! struct Device + //! +-use crate::err_wrapper; + use crate::utils::readlink_value; + use crate::{error::*, DeviceAction}; + use basic::fs_util::{chmod, open_temporary, touch_file}; +@@ -49,7 +48,7 @@ pub const DB_BASE_DIR: &str = "data"; + pub const TAGS_BASE_DIR: &str = "tags"; + + /// Device +-#[derive(Debug, Clone)] ++#[derive(Debug)] + pub struct Device { + /// inotify handler + pub watch_handle: RefCell, +@@ -355,33 +354,16 @@ impl Device { + + let buf_trans: &[u8] = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const _, 16) }; + +- let ifname = String::from_utf8(buf_trans.to_vec()).map_err(|e| Error::Nix { +- msg: format!("from_ifindex failed: from_utf8 {:?} ({})", buf_trans, e), +- source: Errno::EINVAL, ++ let ifname = String::from_utf8(buf_trans.to_vec()).context(FromUtf8 { ++ msg: format!("invalid utf-8 string {:?}", buf_trans), + })?; + + let syspath = format!("/sys/class/net/{}", ifname.trim_matches(char::from(0))); +- let dev = Self::from_syspath(&syspath, true).map_err(|e| Error::Nix { +- msg: format!("from_ifindex failed: {}", e), +- source: e.get_errno(), +- })?; ++ let dev = Self::from_syspath(&syspath, true)?; + +- let i = match dev.get_ifindex() { +- Ok(i) => i, +- Err(e) => { +- if e.get_errno() == Errno::ENOENT { +- return Err(Error::Nix { +- msg: format!("from_ifindex failed: {}", e), +- source: Errno::ENXIO, +- }); +- } +- +- return Err(Error::Nix { +- msg: format!("from_ifindex failed: {}", e), +- source: e.get_errno(), +- }); +- } +- }; ++ let i = dev ++ .get_ifindex() ++ .map_err(|e| e.replace_errno(Errno::ENOENT, Errno::ENXIO))?; + + if i != ifindex { + return Err(Error::Nix { +@@ -393,8 +375,9 @@ impl Device { + Ok(dev) + } + +- /// create a Device instance from subsystem and sysname +- /// if subsystem is 'drivers', sysname should be like 'xxx:yyy' ++ /// Create a Device instance from subsystem and sysname. ++ /// ++ /// If subsystem is 'drivers', sysname should be like 'xxx:yyy' + pub fn from_subsystem_sysname(subsystem: &str, sysname: &str) -> Result { + let sysname = sysname.replace('/', "!"); + if subsystem == "subsystem" { +@@ -506,10 +489,12 @@ impl Device { + }) + } + +- /// set sysattr value ++ /// Set sysattr value. ++ /// ++ /// If the sysattr is not 'uevent', the value will be cached. + pub fn set_sysattr_value(&self, sysattr: &str, value: Option<&str>) -> Result<(), Error> { + if value.is_none() { +- self.remove_cached_sysattr_value(sysattr)?; ++ self.remove_cached_sysattr_value(sysattr); + return Ok(()); + } + +@@ -529,7 +514,7 @@ impl Device { + }; + + if let Err(e) = file.write(value.unwrap().as_bytes()) { +- self.remove_cached_sysattr_value(sysattr)?; ++ self.remove_cached_sysattr_value(sysattr); + return Err(Error::Nix { + msg: format!( + "set_sysattr_value failed: can't write sysattr '{}'", +@@ -559,28 +544,18 @@ impl Device { + + match id.chars().next() { + Some('b') | Some('c') => { +- let devnum = parse_devnum(&id[1..]).map_err(|_| Error::Nix { ++ let devnum = parse_devnum(&id[1..]).context(Basic { + msg: format!("from_device_id failed: parse_devnum '{}' failed", id), +- source: Errno::EINVAL, + })?; + +- return Device::from_devnum(id.chars().next().unwrap(), devnum).map_err(|e| { +- Error::Nix { +- msg: format!("from_device_id failed: {}", e), +- source: e.get_errno(), +- } +- }); ++ Device::from_devnum(id.chars().next().unwrap(), devnum) + } + Some('n') => { +- let ifindex = parse_ifindex(&id[1..]).map_err(|_| Error::Nix { ++ let ifindex = parse_ifindex(&id[1..]).context(Basic { + msg: format!("from_device_id failed: parse_ifindex '{}' failed", id), +- source: Errno::EINVAL, + })?; + +- Device::from_ifindex(ifindex).map_err(|e| Error::Nix { +- msg: format!("from_device_id failed: {}", e), +- source: e.get_errno(), +- }) ++ Device::from_ifindex(ifindex) + } + Some('+') => { + let sep = match id.find(':') { +@@ -604,10 +579,7 @@ impl Device { + + let subsystem = id[1..sep].to_string(); + let sysname = id[sep + 1..].to_string(); +- Device::from_subsystem_sysname(&subsystem, &sysname).map_err(|e| Error::Nix { +- msg: format!("from_device_id failed: {}", e), +- source: e.get_errno(), +- }) ++ Device::from_subsystem_sysname(&subsystem, &sysname) + } + _ => Err(Error::Nix { + msg: format!("from_device_id failed: invalid id '{}'", id), +@@ -648,7 +620,7 @@ impl Device { + /// get the sysname of the device + pub fn get_sysname(&self) -> Result { + if self.sysname.borrow().is_empty() { +- err_wrapper!(self.set_sysname_and_sysnum(), "get_sysname")?; ++ self.set_sysname_and_sysnum()?; + } + + Ok(self.sysname.borrow().clone()) +@@ -729,10 +701,7 @@ impl Device { + // e.g. /sys/devices/pci0000:00/0000:00:10.0/host2/target2:0:1/2:0:1:0/block/sda/subsystem -> ../../../../../../../../class/block + // get `block` + let filename = if Path::exists(Path::new(subsystem_path)) { +- readlink_value(subsystem_path).map_err(|e| Error::Nix { +- msg: format!("get_subsystem failed: {}", e), +- source: e.get_errno(), +- })? ++ readlink_value(subsystem_path)? + } else { + "".to_string() + }; +@@ -766,10 +735,7 @@ impl Device { + + /// get the ifindex of device + pub fn get_ifindex(&self) -> Result { +- self.read_uevent_file().map_err(|e| Error::Nix { +- msg: format!("get_ifindex failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_uevent_file()?; + + if *self.ifindex.borrow() == 0 { + return Err(Error::Nix { +@@ -802,12 +768,7 @@ impl Device { + + /// get devnum + pub fn get_devnum(&self) -> Result { +- match self.read_uevent_file() { +- Ok(_) => {} +- Err(e) => { +- return Err(e); +- } +- } ++ self.read_uevent_file()?; + + if major(*self.devnum.borrow()) == 0 { + return Err(Error::Nix { +@@ -870,10 +831,7 @@ impl Device { + /// get device sysnum + pub fn get_sysnum(&self) -> Result { + if self.sysname.borrow().is_empty() { +- self.set_sysname_and_sysnum().map_err(|e| Error::Nix { +- msg: format!("get_sysnum failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.set_sysname_and_sysnum()?; + } + + if self.sysnum.borrow().is_empty() { +@@ -915,10 +873,7 @@ impl Device { + + /// get device diskseq + pub fn get_diskseq(&self) -> Result { +- self.read_uevent_file().map_err(|e| Error::Nix { +- msg: format!("get_diskseq failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_uevent_file()?; + + if *self.diskseq.borrow() == 0 { + return Err(Error::Nix { +@@ -935,7 +890,6 @@ impl Device { + + /// get is initialized + pub fn get_is_initialized(&self) -> Result { +- // match self.read_db + match self.read_db() { + Ok(_) => {} + Err(e) => { +@@ -979,30 +933,21 @@ impl Device { + + /// check whether the device has the tag + pub fn has_tag(&self, tag: &str) -> Result { +- self.read_db().map_err(|e| Error::Nix { +- msg: format!("has_tag failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_db()?; + + Ok(self.all_tags.borrow().contains(tag)) + } + + /// check whether the device has the current tag + pub fn has_current_tag(&self, tag: &str) -> Result { +- self.read_db().map_err(|e| Error::Nix { +- msg: format!("has_tag failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_db()?; + + Ok(self.current_tags.borrow().contains(tag)) + } + + /// get the value of specific device property + pub fn get_property_value(&self, key: &str) -> Result { +- self.properties_prepare().map_err(|e| Error::Nix { +- msg: format!("get_property_value failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.properties_prepare()?; + + match self.properties.borrow().get(key) { + Some(v) => Ok(v.clone()), +@@ -1083,27 +1028,22 @@ impl Device { + let mut file = std::fs::OpenOptions::new() + .read(true) + .open(&sysattr_path) +- .map_err(|e| Error::Nix { ++ .context(Io { + msg: format!( +- "get_sysattr_value failed: can't open sysattr '{}': {}", +- sysattr, e ++ "get_sysattr_value failed: can't open sysattr '{}'", ++ sysattr + ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), + })?; + let mut value = String::new(); +- file.read_to_string(&mut value).map_err(|e| Error::Nix { +- msg: format!( +- "get_sysattr_value failed: can't read sysattr '{}': {}", +- sysattr, e +- ), +- source: Errno::from_i32(e.raw_os_error().unwrap_or_default()), ++ file.read_to_string(&mut value).context(Io { ++ msg: format!("get_sysattr_value failed: can't read sysattr '{}'", sysattr), + })?; + value.trim_end().to_string() + } + } + + Err(e) => { +- self.remove_cached_sysattr_value(sysattr).unwrap(); ++ self.remove_cached_sysattr_value(sysattr); + return Err(Error::Nix { + msg: format!("get_sysattr_value failed: can't lstat '{}'", sysattr_path), + source: e, +@@ -1111,11 +1051,7 @@ impl Device { + } + }; + +- self.cache_sysattr_value(sysattr, &value) +- .map_err(|e| Error::Nix { +- msg: format!("get_sysattr_value failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.cache_sysattr_value(sysattr, &value)?; + + Ok(value) + } +@@ -1133,15 +1069,9 @@ impl Device { + + 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 id = randomize().context(Nix { ++ msg: "Failed to randomize".to_string(), ++ })?; + + let j = s + " " + &id.to_string(); + +@@ -1152,33 +1082,12 @@ impl Device { + + /// open device + pub fn open(&self, oflags: OFlag) -> Result { +- let devname = self.get_devname().map_err(|e| { +- if e.get_errno() == Errno::ENOENT { +- Error::Nix { +- msg: format!("open failed: {}", e), +- source: Errno::ENOEXEC, +- } +- } else { +- Error::Nix { +- msg: format!("open failed: {}", e), +- source: e.get_errno(), +- } +- } +- })?; +- +- let devnum = self.get_devnum().map_err(|e| { +- if e.get_errno() == Errno::ENOENT { +- Error::Nix { +- msg: format!("open failed: {}", e), +- source: Errno::ENOEXEC, +- } +- } else { +- Error::Nix { +- msg: format!("open failed: {}", e), +- source: e.get_errno(), +- } +- } +- })?; ++ let devname = self ++ .get_devname() ++ .map_err(|e| e.replace_errno(Errno::ENOENT, Errno::ENOEXEC))?; ++ let devnum = self ++ .get_devnum() ++ .map_err(|e| e.replace_errno(Errno::ENOENT, Errno::ENOEXEC))?; + + let subsystem = match self.get_subsystem() { + Ok(s) => s, +@@ -1212,19 +1121,13 @@ impl Device { + } + }; + +- let stat = match nix::sys::stat::fstat(file.as_raw_fd()) { +- Ok(s) => s, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!( +- "open failed: can't fstat fd {} for '{}'", +- file.as_raw_fd(), +- devname +- ), +- source: e, +- }) +- } +- }; ++ let stat = nix::sys::stat::fstat(file.as_raw_fd()).context(Nix { ++ msg: format!( ++ "open failed: can't fstat fd {} for '{}'", ++ file.as_raw_fd(), ++ devname ++ ), ++ })?; + + if stat.st_rdev != devnum { + return Err(Error::Nix { +@@ -1265,18 +1168,11 @@ impl Device { + + let mut diskseq: u64 = 0; + +- if self.get_is_initialized().map_err(|e| Error::Nix { +- msg: format!("open failed: {}", e), +- source: e.get_errno(), +- })? { ++ if self.get_is_initialized()? { + match self.get_property_value("ID_IGNORE_DISKSEQ") { + Ok(value) => { +- if !value.parse::().map_err(|e| Error::Nix { +- msg: format!( +- "open failed: failed to parse value '{}' to boolean: {}", +- value, e +- ), +- source: Errno::EINVAL, ++ if !value.parse::().context(ParseBool { ++ msg: format!("invalid value '{}'", value), + })? { + match self.get_diskseq() { + Ok(n) => diskseq = n, +@@ -1302,28 +1198,16 @@ impl Device { + } + } + +- let file2 = +- basic::fd_util::fd_reopen(file.as_raw_fd(), oflags).map_err(|e| Error::Nix { +- msg: format!("open failed: {}", e), +- source: match e { +- basic::Error::Nix { source } => source, +- _ => Errno::EINVAL, +- }, +- })?; ++ let file2 = basic::fd_util::fd_reopen(file.as_raw_fd(), oflags).context(Basic { ++ msg: format!("failed to open {}", file.as_raw_fd()), ++ })?; + + if diskseq == 0 { + return Ok(file2); + } + +- let q = basic::fd_util::fd_get_diskseq(file2.as_raw_fd()).map_err(|e| Error::Nix { +- msg: format!( +- "open failed: failed to get diskseq on fd {}", +- file2.as_raw_fd() +- ), +- source: match e { +- basic::Error::Nix { source } => source, +- _ => Errno::EINVAL, +- }, ++ let q = basic::fd_util::fd_get_diskseq(file2.as_raw_fd()).context(Basic { ++ msg: format!("failed to get diskseq on fd {}", file2.as_raw_fd()), + })?; + + if q != diskseq { +@@ -1352,18 +1236,9 @@ impl Device { + + /// shadow clone a device object and import properties from db + pub fn clone_with_db(&self) -> Result { +- let device = self.shallow_clone().map_err(|e| Error::Nix { +- msg: format!("clone_with_db failed: {}", e), +- source: e.get_errno(), +- })?; +- +- device.read_db().map_err(|e| Error::Nix { +- msg: format!("clone_with_db failed: {}", e), +- source: e.get_errno(), +- })?; +- ++ let device = self.shallow_clone()?; ++ device.read_db()?; + device.sealed.replace(true); +- + Ok(device) + } + +@@ -1427,22 +1302,14 @@ impl Device { + /// + /// The format is like: + /// +- /// character device: c: +- /// +- /// block device: b: +- /// +- /// network interface: n +- /// +- /// drivers: +drivers:: +- /// +- /// other subsystems: +: +- /// ++ /// - character device: c: ++ /// - block device: b: ++ /// - network interface: n ++ /// - drivers: +drivers:: ++ /// - other subsystems: +: + pub fn get_device_id(&self) -> Result { + if self.device_id.borrow().is_empty() { +- let subsystem = self.get_subsystem().map_err(|e| Error::Nix { +- msg: format!("get_device_id failed: {}", e), +- source: e.get_errno(), +- })?; ++ let subsystem = self.get_subsystem()?; + + let id: String; + if let Ok(devnum) = self.get_devnum() { +@@ -1455,10 +1322,7 @@ impl Device { + } else if let Ok(ifindex) = self.get_ifindex() { + id = format!("n{}", ifindex); + } else { +- let sysname = self.get_sysname().map_err(|e| Error::Nix { +- msg: format!("get_device_id failed: {}", e), +- source: e.get_errno(), +- })?; ++ let sysname = self.get_sysname()?; + + if subsystem == "drivers" { + id = format!("+drivers:{}:{}", self.driver_subsystem.borrow(), sysname); +@@ -1571,21 +1435,14 @@ impl Device { + let db_path = format!("{}/{}/{}", self.base_path.borrow(), DB_BASE_DIR, id); + + if !has_info && *self.devnum.borrow() == 0 && *self.ifindex.borrow() == 0 { +- unlink(db_path.as_str()).map_err(|e| Error::Nix { ++ unlink(db_path.as_str()).context(Nix { + msg: format!("update_db failed: can't unlink db '{}'", db_path), +- source: e, + })?; +- + return Ok(()); + } + +- create_dir_all(&format!("{}/{}", self.base_path.borrow(), DB_BASE_DIR)).map_err(|e| { +- Error::Nix { +- msg: "update_db failed: can't create db directory".to_string(), +- source: e +- .raw_os_error() +- .map_or_else(|| nix::Error::EIO, nix::Error::from_i32), +- } ++ create_dir_all(&format!("{}/{}", self.base_path.borrow(), DB_BASE_DIR)).context(Io { ++ msg: "failed to create db directory".to_string(), + })?; + + if let Err(e) = chmod( +@@ -1595,22 +1452,14 @@ impl Device { + log::error!("Failed to set permission for /run/devmaster/data/: {}", e); + } + +- let (mut file, tmp_file) = open_temporary(&db_path).map_err(|e| { +- let errno = match e { +- basic::error::Error::Nix { source } => source, +- _ => nix::Error::EINVAL, +- }; +- Error::Nix { +- msg: "update_db failed: can't open temporary file".to_string(), +- source: errno, +- } ++ let (mut file, tmp_file) = open_temporary(&db_path).context(Basic { ++ msg: "can't open temporary file".to_string(), + })?; + +- self.atomic_create_db(&mut file, tmp_file.as_str(), db_path.as_str()) +- .map_err(|e| { +- Self::cleanup(&db_path, &tmp_file); +- e +- })?; ++ if let Err(e) = self.atomic_create_db(&mut file, tmp_file.as_str(), db_path.as_str()) { ++ Self::cleanup(&db_path, &tmp_file); ++ return Err(e); ++ } + + Ok(()) + } +@@ -1713,9 +1562,8 @@ impl Device { + ); + + if add { +- touch_file(&tag_path, true, Some(0o444), None, None).map_err(|e| Error::Nix { +- msg: format!("tag_persist failed: can't touch file '{}': {}", tag_path, e), +- source: nix::Error::EINVAL, ++ touch_file(&tag_path, true, Some(0o444), None, None).context(Basic { ++ msg: format!("can't touch file '{}'", tag_path), + })?; + + if let Err(e) = chmod( +@@ -1765,10 +1613,7 @@ impl Device { + + /// read database + pub fn read_db(&self) -> Result<(), Error> { +- self.read_db_internal(false).map_err(|e| Error::Nix { +- msg: format!("read_db failed: {}", e), +- source: e.get_errno(), +- }) ++ self.read_db_internal(false) + } + + /// read database internally +@@ -1777,18 +1622,11 @@ impl Device { + return Ok(()); + } + +- let id = self.get_device_id().map_err(|e| Error::Nix { +- msg: format!("read_db_internal failed: {}", e), +- source: e.get_errno(), +- })?; ++ let id = self.get_device_id()?; + + let path = format!("{}/{}/{}", self.base_path.borrow(), DB_BASE_DIR, id); + + self.read_db_internal_filename(&path) +- .map_err(|e| Error::Nix { +- msg: format!("read_db_internal failed: {}", e), +- source: e.get_errno(), +- }) + } + + /// get properties nulstr, if it is out of date, update it +@@ -1836,10 +1674,7 @@ impl Device { + } + + // verify subsystem +- let subsystem_ret = device.get_subsystem().map_err(|e| Error::Nix { +- msg: format!("from_mode_and_devnum failed: {}", e), +- source: e.get_errno(), +- })?; ++ let subsystem_ret = device.get_subsystem()?; + if (subsystem_ret == "block") != ((mode & S_IFMT) == S_IFBLK) { + return Err(Error::Nix { + msg: "from_mode_and_devnum failed: inconsistent subsystem".to_string(), +@@ -1942,19 +1777,9 @@ impl Device { + + /// set the sysname and sysnum of device object + pub fn set_sysname_and_sysnum(&self) -> Result<(), Error> { +- let sysname = match self.devpath.borrow().rfind('/') { +- Some(i) => String::from(&self.devpath.borrow()[i + 1..]), +- None => { +- return Err(Error::Nix { +- msg: format!( +- "set_sysname_and_sysnum failed: invalid devpath '{}'", +- self.devpath.borrow() +- ), +- source: Errno::EINVAL, +- }); +- } +- }; +- ++ /* The devpath is validated to begin with '/' when setting syspath. */ ++ let idx = self.devpath.borrow().rfind('/').unwrap(); ++ let sysname = String::from(&self.devpath.borrow()[idx + 1..]); + let sysname = sysname.replace('!', "/"); + + let mut ridx = sysname.len(); +@@ -2146,15 +1971,10 @@ impl Device { + /// set ifindex + pub fn set_ifindex(&self, ifindex: &str) -> Result<(), Error> { + self.add_property_internal("IFINDEX", ifindex).unwrap(); +- self.ifindex.replace(match ifindex.parse::() { +- Ok(idx) => idx, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("set_ifindex failed: {}", e), +- source: Errno::EINVAL, +- }); +- } +- }); ++ self.ifindex ++ .replace(ifindex.parse::().context(ParseInt { ++ msg: format!("invalid integer '{}'", ifindex), ++ })?); + Ok(()) + } + +@@ -2172,12 +1992,8 @@ impl Device { + + /// set devmode + pub fn set_devmode(&self, devmode: &str) -> Result<(), Error> { +- let m = Some(mode_t::from_str_radix(devmode, 8).map_err(|e| Error::Nix { +- msg: format!( +- "set_devmode failed: can't change '{}' to mode: {}", +- devmode, e +- ), +- source: Errno::EINVAL, ++ let m = Some(mode_t::from_str_radix(devmode, 8).context(ParseInt { ++ msg: format!("invalid octal mode '{}'", devmode), + })?); + + self.devmode.replace(m); +@@ -2189,9 +2005,8 @@ impl Device { + + /// set device uid + pub fn set_devuid(&self, devuid: &str) -> Result<(), Error> { +- let uid = devuid.parse::().map_err(|e| Error::Nix { +- msg: format!("set_devuid failed: can't change '{}' to uid: {}", devuid, e), +- source: Errno::EINVAL, ++ let uid = devuid.parse::().context(ParseInt { ++ msg: format!("invalid uid '{}'", devuid), + })?; + + self.devuid.replace(Some(Uid::from_raw(uid))); +@@ -2203,9 +2018,8 @@ impl Device { + + /// set device gid + pub fn set_devgid(&self, devgid: &str) -> Result<(), Error> { +- let gid = devgid.parse::().map_err(|e| Error::Nix { +- msg: format!("set_devgid failed: can't change '{}' to gid: {}", devgid, e), +- source: Errno::EINVAL, ++ let gid = devgid.parse::().context(ParseInt { ++ msg: format!("invalid gid '{}'", devgid), + })?; + + self.devgid.replace(Some(Gid::from_raw(gid))); +@@ -2217,15 +2031,10 @@ impl Device { + + /// set devnum + pub fn set_devnum(&self, major: &str, minor: &str) -> Result<(), Error> { +- let major_num: u64 = match major.parse() { +- Ok(n) => n, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("set_devnum failed: invalid major number '{}': {}", major, e), +- source: Errno::EINVAL, +- }); +- } +- }; ++ let major_num: u64 = major.parse().context(ParseInt { ++ msg: format!("invalid major number '{}'", major), ++ })?; ++ + let minor_num: u64 = match minor.parse() { + Ok(n) => n, + Err(e) => { +@@ -2247,15 +2056,9 @@ impl Device { + pub fn set_diskseq(&self, diskseq: &str) -> Result<(), Error> { + self.add_property_internal("DISKSEQ", diskseq).unwrap(); + +- let diskseq_num: u64 = match diskseq.parse() { +- Ok(n) => n, +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("set_diskseq failed: invalid diskseq '{}': {}", diskseq, e), +- source: Errno::EINVAL, +- }); +- } +- }; ++ let diskseq_num: u64 = diskseq.parse().context(ParseInt { ++ msg: format!("invalid diskseq '{}'", diskseq), ++ })?; + + self.diskseq.replace(diskseq_num); + +@@ -2271,18 +2074,7 @@ impl Device { + + /// set action from string + pub fn set_action_from_string(&self, action_s: &str) -> Result<(), Error> { +- let action = match action_s.parse::() { +- Ok(a) => a, +- Err(_) => { +- return Err(Error::Nix { +- msg: format!( +- "set_action_from_string failed: invalid action '{}'", +- action_s +- ), +- source: Errno::EINVAL, +- }); +- } +- }; ++ let action = action_s.parse::()?; + + self.set_action(action); + +@@ -2291,19 +2083,9 @@ impl Device { + + /// set seqnum from string + pub fn set_seqnum_from_string(&self, seqnum_s: &str) -> Result<(), Error> { +- let seqnum: u64 = match seqnum_s.parse() { +- Ok(n) => n, +- Err(_) => { +- return Err(Error::Nix { +- msg: format!( +- "set_seqnum_from_string failed: invalid seqnum '{}'", +- seqnum_s +- ), +- source: Errno::EINVAL, +- }); +- } +- }; +- ++ let seqnum: u64 = seqnum_s.parse().context(ParseInt { ++ msg: format!("invalid seqnum '{}'", seqnum_s), ++ })?; + self.set_seqnum(seqnum); + Ok(()) + } +@@ -2325,7 +2107,7 @@ impl Device { + /// cache sysattr value + pub fn cache_sysattr_value(&self, sysattr: &str, value: &str) -> Result<(), Error> { + if value.is_empty() { +- self.remove_cached_sysattr_value(sysattr)?; ++ self.remove_cached_sysattr_value(sysattr); + } else { + self.sysattr_values + .borrow_mut() +@@ -2336,34 +2118,27 @@ impl Device { + } + + /// remove cached sysattr value +- pub fn remove_cached_sysattr_value(&self, sysattr: &str) -> Result<(), Error> { ++ pub fn remove_cached_sysattr_value(&self, sysattr: &str) { + self.sysattr_values.borrow_mut().remove(sysattr); +- +- Ok(()) + } + + /// get cached sysattr value + pub fn get_cached_sysattr_value(&self, sysattr: &str) -> Result { + if !self.sysattr_values.borrow().contains_key(sysattr) { + return Err(Error::Nix { +- msg: format!( +- "get_cached_sysattr_value failed: no cached sysattr '{}'", +- sysattr +- ), ++ msg: format!("no cached sysattr '{}'", sysattr), + source: Errno::ESTALE, + }); + } + +- match self.sysattr_values.borrow().get(sysattr) { +- Some(value) => Ok(value.clone()), +- None => Err(Error::Nix { +- msg: format!( +- "get_cached_sysattr_value failed: non-existing sysattr '{}'", +- sysattr +- ), ++ self.sysattr_values ++ .borrow() ++ .get(sysattr) ++ .cloned() ++ .ok_or(Error::Nix { ++ msg: format!("non-existing sysattr '{}'", sysattr), + source: Errno::ENOENT, +- }), +- } ++ }) + } + + /// new from child +@@ -2378,8 +2153,7 @@ impl Device { + Some(p) => { + if p == Path::new("/sys") { + return Err(Error::Nix { +- msg: "new_from_child failed: no available parent device until /sys" +- .to_string(), ++ msg: "no available parent device".to_string(), + source: Errno::ENODEV, + }); + } +@@ -2387,7 +2161,7 @@ impl Device { + let path = p + .to_str() + .ok_or(Error::Nix { +- msg: format!("new_from_child failed: invalid path '{:?}'", p), ++ msg: format!("invalid path '{:?}'", p), + source: Errno::ENODEV, + })? + .to_string(); +@@ -2444,15 +2218,9 @@ impl Device { + /// 3. if self devlinks are outdated, add to internal property + /// 4. if self tags are outdated ,add to internal property + pub fn properties_prepare(&self) -> Result<(), Error> { +- self.read_uevent_file().map_err(|e| Error::Nix { +- msg: format!("properties_prepare failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_uevent_file()?; + +- self.read_db().map_err(|e| Error::Nix { +- msg: format!("properties_prepare failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.read_db()?; + + let property_devlinks_outdated = *self.property_devlinks_outdated.borrow(); + if property_devlinks_outdated { +@@ -2518,15 +2286,8 @@ impl Device { + }; + + let mut buf = String::new(); +- file.read_to_string(&mut buf).map_err(|e| Error::Nix { +- msg: format!( +- "read_db_internal_filename failed: can't read db '{}': {}", +- filename, e +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), ++ file.read_to_string(&mut buf).context(Io { ++ msg: format!("can't read db '{}'", filename), + })?; + + self.is_initialized.replace(true); +@@ -2540,10 +2301,7 @@ impl Device { + let key = &line[0..1]; + let value = &line[2..]; + +- self.handle_db_line(key, value).map_err(|e| Error::Nix { +- msg: format!("read_db_internal_filename failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.handle_db_line(key, value)?; + } + + Ok(()) +@@ -2572,29 +2330,18 @@ impl Device { + + let (k, v) = (tokens[0], tokens[1]); + +- self.add_property_internal(k, v).map_err(|e| Error::Nix { +- msg: format!("handle_db_line failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.add_property_internal(k, v)?; + } + "I" => { +- let time = value.parse::().map_err(|e| Error::Nix { +- msg: format!( +- "handle_db_line failed: invalid initialized time '{}': {}", +- value, e +- ), +- source: Errno::EINVAL, ++ let time = value.parse::().context(ParseInt { ++ msg: format!("invalid usec integer '{}'", value), + })?; + + self.set_usec_initialized(time); + } + "L" => { +- let priority = value.parse::().map_err(|e| Error::Nix { +- msg: format!( +- "handle_db_line failed: failed to parse devlink priority '{}': {}", +- value, e +- ), +- source: Errno::EINVAL, ++ let priority = value.parse::().context(ParseInt { ++ msg: format!("invalid link priority integer '{}'", value), + })?; + + self.devlink_priority.replace(priority); +@@ -2603,12 +2350,8 @@ impl Device { + log::debug!("watch handle in database is deprecated."); + } + "V" => { +- let version = value.parse::().map_err(|e| Error::Nix { +- msg: format!( +- "handle_db_line failed: failed to parse database version '{}': {}", +- value, e +- ), +- source: Errno::EINVAL, ++ let version = value.parse::().context(ParseInt { ++ msg: format!("invalid db version integer '{}'", value), + })?; + + self.database_version.replace(version); +@@ -2625,22 +2368,11 @@ impl Device { + pub fn shallow_clone(&self) -> Result { + let device = Self::default(); + +- let syspath = self.get_syspath().map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ let syspath = self.get_syspath()?; + +- device +- .set_syspath(&syspath, false) +- .map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ device.set_syspath(&syspath, false)?; + +- let subsystem = self.get_subsystem().map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ let subsystem = self.get_subsystem()?; + + device.set_subsystem(&subsystem); + +@@ -2651,24 +2383,15 @@ impl Device { + } + + if let Ok(ifindex) = self.get_property_value("IFINDEX") { +- device.set_ifindex(&ifindex).map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: failed to set_ifindex ({})", e), +- source: e.get_errno(), +- })?; ++ device.set_ifindex(&ifindex)?; + } + + if let Ok(major) = self.get_property_value("MAJOR") { + let minor = self.get_property_value("MINOR")?; +- device.set_devnum(&major, &minor).map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ device.set_devnum(&major, &minor)?; + } + +- device.read_uevent_file().map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ device.read_uevent_file()?; + + Ok(device) + } +@@ -2685,12 +2408,8 @@ impl Device { + "DRIVER" => self.set_driver(value), + "IFINDEX" => self.set_ifindex(value)?, + "USEC_INITIALIZED" => { +- self.set_usec_initialized(value.parse::().map_err(|e| Error::Nix { +- msg: format!( +- "amend_key_value failed: failed to parse initialized time '{}': {}", +- value, e +- ), +- source: Errno::EINVAL, ++ self.set_usec_initialized(value.parse::().context(ParseInt { ++ msg: format!("invalid usec integer '{}'", value), + })?); + } + "DEVMODE" => self.set_devmode(value)?, +@@ -2814,9 +2533,8 @@ impl Device { + format!("{}/{}", syspath, subdir) + }; + +- std::fs::read_dir(&dir).map_err(|e| Error::Nix { ++ std::fs::read_dir(&dir).context(Io { + msg: format!("Failed to read directory '{}'", &dir), +- source: nix::Error::from_i32(e.raw_os_error().unwrap_or_default()), + }) + } + +@@ -3017,7 +2735,7 @@ impl Device { + log::debug!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or(self.devpath.borrow().clone()), ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ) + } +@@ -3033,7 +2751,7 @@ impl Device { + log::error!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or(self.devpath.borrow().clone()), ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ) + } +@@ -3049,7 +2767,7 @@ impl Device { + log::debug!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or(self.devpath.borrow().clone()), ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ) + } +@@ -3065,7 +2783,7 @@ impl Device { + log::debug!( + "failed to prepare properties of '{}': {}", + self.get_device_id() +- .unwrap_or(self.devpath.borrow().clone()), ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ) + } +@@ -3081,7 +2799,7 @@ impl Device { + log::debug!( + "failed to enumerate children of '{}': {}", + self.get_device_id() +- .unwrap_or(self.devpath.borrow().clone()), ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ) + } +@@ -3097,7 +2815,8 @@ impl Device { + if let Err(e) = self.read_all_sysattrs() { + log::debug!( + "{}: failed to read all sysattrs: {}", +- self.get_sysname().unwrap_or(self.devpath.borrow().clone()), ++ self.get_sysname() ++ .unwrap_or_else(|_| self.devpath.borrow().clone()), + e + ); + } +@@ -3117,6 +2836,7 @@ impl PartialEq for Device { + + #[cfg(test)] + mod tests { ++ use std::fs::OpenOptions; + use std::panic::catch_unwind; + + use crate::{ +@@ -3426,6 +3146,11 @@ mod tests { + nix::Error::EINVAL + ); + ++ /* Test other add_* methods. */ ++ device.add_property("A", "AA").unwrap(); ++ device.add_property("B", "BB").unwrap(); ++ device.add_tags("A:B:C", true); ++ + device.update_db().unwrap(); + + /* Test enumerating child devices */ +@@ -3902,8 +3627,8 @@ Q:devmaster + V:100 + "; + touch_file("/tmp/tmp_db", false, None, None, None).unwrap(); +- let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); +- f.write(content.as_bytes()).unwrap(); ++ let mut f = OpenOptions::new().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); + let device = Device::new(); + device.read_db_internal_filename("/tmp/tmp_db").unwrap(); + } +@@ -3912,8 +3637,8 @@ V:100 + { + let content = "error + "; +- let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); +- f.write(content.as_bytes()).unwrap(); ++ let mut f = OpenOptions::new().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); + let device = Device::new(); + device.read_db_internal_filename("/tmp/tmp_db").unwrap(); + } +@@ -3922,13 +3647,13 @@ V:100 + { + let content = "I:invalid + "; +- let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); +- f.write(content.as_bytes()).unwrap(); ++ let mut f = OpenOptions::new().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); + let device = Device::new(); + assert!(device.read_db_internal_filename("/tmp/tmp_db").is_err()); + } + +- /* DB shoud be readable. */ ++ /* DB should be readable. */ + { + touch_file("/tmp/tmp_db_writeonly", false, Some(0o222), None, None).unwrap(); + let device = Device::new(); +@@ -3973,4 +3698,98 @@ V:100 + }) + .is_err()); + } ++ ++ #[test] ++ fn test_set() { ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ device.set_devuid("1").unwrap(); ++ device.set_devgid("1").unwrap(); ++ device.set_devmode("666").unwrap(); ++ device.set_diskseq("1").unwrap(); ++ device.set_action_from_string("change").unwrap(); ++ device.set_sysattr_value("ifalias", Some("test")).unwrap(); ++ ++ assert_eq!(&device.get_property_value("DEVUID").unwrap(), "1"); ++ assert_eq!(&device.get_property_value("DEVGID").unwrap(), "1"); ++ assert_eq!(&device.get_property_value("DEVMODE").unwrap(), "666"); ++ assert_eq!(&device.get_property_value("DISKSEQ").unwrap(), "1"); ++ assert_eq!(&device.get_property_value("ACTION").unwrap(), "change"); ++ assert_eq!(&device.get_cached_sysattr_value("ifalias").unwrap(), "test"); ++ ++ assert!(device.set_devuid("invalid").is_err()); ++ assert!(device.set_devgid("invalid").is_err()); ++ assert!(device.set_devmode("invalid").is_err()); ++ assert!(device.set_diskseq("invalid").is_err()); ++ assert!(device.set_action_from_string("invalid").is_err()); ++ assert!(device.set_sysattr_value("nonexist", Some("test")).is_err()); ++ ++ assert!(device.set_sysattr_value("nonexist", None).is_ok()); ++ assert!(device.set_sysattr_value("ifalias", None).is_ok()); ++ } ++ ++ #[test] ++ fn test_from_device_id() { ++ assert!(Device::from_device_id("invalid").is_err()); ++ assert!(Device::from_device_id("b").is_err()); ++ assert!(Device::from_device_id("+drivers").is_err()); ++ assert!(Device::from_device_id("+drivers:").is_err()); ++ assert!(Device::from_device_id("+drivers::usb").is_err()); ++ ++ let dev = Device::from_device_id("+drivers:usb:usb").unwrap(); ++ println!("{}", dev.get_device_id().unwrap()); ++ ++ let dev = Device::from_syspath("/sys/bus/usb/drivers/usb", true).unwrap(); ++ println!("{}", dev.get_device_id().unwrap()); ++ ++ let _ = unlink("/tmp/devmaster/data/+drivers:usb:usb"); ++ dev.set_base_path("/tmp/devmaster"); ++ assert!(dev.update_db().is_err()); ++ dev.add_property("hello", "world").unwrap(); ++ dev.update_db().unwrap(); ++ assert!(Path::new("/tmp/devmaster/data/+drivers:usb:usb").exists()); ++ } ++ ++ #[test] ++ fn test_get_err() { ++ let device = Device::new(); ++ assert!(device.get_syspath().is_err()); ++ assert!(device.get_devpath().is_err()); ++ assert!(device.get_parent().is_err()); ++ assert!(device.get_devtype().is_err()); ++ assert!(!device.get_is_initialized().unwrap()); ++ } ++ ++ #[test] ++ fn test_cleanup() { ++ let _ = touch_file("/tmp/devmaster/a", false, None, None, None); ++ let _ = touch_file("/tmp/devmaster/b", false, None, None, None); ++ Device::cleanup("/tmp/devmaster/a", "/tmp/devmaster/b"); ++ } ++ ++ #[test] ++ fn test_fmt() { ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ println!("{:?}", device); ++ } ++ ++ #[test] ++ fn test_set_syspath_no_verify() { ++ let device = Device::new(); ++ device.set_syspath("/sys/test", false).unwrap(); ++ ++ assert!(device.set_sysname_and_sysnum().is_ok()); ++ } ++ ++ #[test] ++ fn test_partial_eq_trait() { ++ let dev1 = Device::from_syspath("/sys/class/net/lo", true).unwrap(); ++ let dev2 = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ++ assert!(dev1 == dev2); ++ } ++ ++ #[test] ++ fn test_from_devnum_err() { ++ assert!(Device::from_devnum('x', 100).is_err()); ++ } + } +diff --git a/libs/device/src/device_enumerator.rs b/libs/device/src/device_enumerator.rs +index 96cac2d9..55f8681d 100644 +--- a/libs/device/src/device_enumerator.rs ++++ b/libs/device/src/device_enumerator.rs +@@ -12,7 +12,7 @@ + + //! enumerate /sys to collect devices + //! +-use crate::{device::Device, err_wrapper, error::Error, utils::*}; ++use crate::{device::Device, error::Error, utils::*}; + use bitflags::bitflags; + use fnmatch_sys::fnmatch; + use nix::errno::Errno; +@@ -45,7 +45,7 @@ impl Default for MatchInitializedType { + } + + /// enumerate devices or subsystems under /sys +-#[derive(Debug, Default)] ++#[derive(Default)] + pub struct DeviceEnumerator { + /// enumerator type + pub(crate) etype: RefCell, +@@ -289,7 +289,7 @@ impl DeviceEnumerator { + + /// add match parent + pub fn add_match_parent_incremental(&mut self, parent: &Device) -> Result<(), Error> { +- let syspath = err_wrapper!(parent.get_syspath(), "add_match_parent_incremental")?; ++ let syspath = parent.get_syspath()?; + self.match_parent.borrow_mut().insert(syspath); + self.scan_up_to_date.replace(false); + Ok(()) +@@ -364,7 +364,7 @@ impl DeviceEnumerator { + // remove already sorted devices from the hashmap (self.devices_by_syspath) + // avoid get repeated devices from the hashmap later + for device in devices.iter().skip(m) { +- let syspath = err_wrapper!(device.borrow().get_syspath(), "sort_devices")?; ++ let syspath = device.borrow().get_syspath()?; + + self.devices_by_syspath.borrow_mut().remove(&syspath); + } +@@ -400,7 +400,7 @@ impl DeviceEnumerator { + + /// add device + pub(crate) fn add_device(&self, device: Rc>) -> Result { +- let syspath = err_wrapper!(device.borrow().get_syspath(), "add_device")?; ++ let syspath = device.borrow().get_syspath()?; + + match self.devices_by_syspath.borrow_mut().insert(syspath, device) { + Some(_) => { +diff --git a/libs/device/src/device_monitor.rs b/libs/device/src/device_monitor.rs +index 729a7dcb..db51c40f 100644 +--- a/libs/device/src/device_monitor.rs ++++ b/libs/device/src/device_monitor.rs +@@ -250,7 +250,7 @@ mod tests { + /// + fn dispatch(&self, e: &Events) -> i32 { + let device = self.device_monitor.receive_device().unwrap(); +- println!("{:?}", device); ++ println!("{}", device.get_device_id().unwrap()); + e.set_exit(); + 0 + } +diff --git a/libs/device/src/error.rs b/libs/device/src/error.rs +index 40589c21..10410301 100644 +--- a/libs/device/src/error.rs ++++ b/libs/device/src/error.rs +@@ -38,6 +38,24 @@ pub enum Error { + + #[snafu(context, display("Basic error: {}", msg))] + Basic { msg: String, source: basic::Error }, ++ ++ #[snafu(context, display("Failed to parse boolean: {}", msg))] ++ ParseBool { ++ msg: String, ++ source: std::str::ParseBoolError, ++ }, ++ ++ #[snafu(context, display("Failed to parse integer: {}", msg))] ++ ParseInt { ++ msg: String, ++ source: std::num::ParseIntError, ++ }, ++ ++ #[snafu(context, display("Failed to parse utf-8: {}", msg))] ++ FromUtf8 { ++ msg: String, ++ source: std::string::FromUtf8Error, ++ }, + } + + impl Error { +@@ -53,21 +71,13 @@ impl Error { + source: errno, + } => Errno::from_i32(errno.raw_os_error().unwrap_or_default()), + Self::Basic { msg: _, source } => Errno::from_i32(source.get_errno()), ++ Self::ParseBool { msg: _, source: _ } => nix::Error::EINVAL, ++ Self::ParseInt { msg: _, source: _ } => nix::Error::EINVAL, ++ Self::FromUtf8 { msg: _, source: _ } => nix::Error::EINVAL, + } + } + } + +-/// append current function and inherit the errno +-#[macro_export] +-macro_rules! err_wrapper { +- ($e:expr, $s:expr) => { +- $e.map_err(|e| Error::Nix { +- msg: format!("$s failed: {}", e), +- source: e.get_errno(), +- }) +- }; +-} +- + impl Error { + /// check whether the device error belongs to specific errno + pub fn is_errno(&self, errno: nix::Error) -> bool { +@@ -81,4 +91,37 @@ impl Error { + Errno::ENODEV | Errno::ENXIO | Errno::ENOENT + ) + } ++ ++ pub(crate) fn replace_errno(self, from: Errno, to: Errno) -> Self { ++ let n = self.get_errno(); ++ ++ if n == from { ++ Self::Nix { ++ msg: self.to_string(), ++ source: to, ++ } ++ } else { ++ self ++ } ++ } ++} ++ ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ use nix::errno::Errno; ++ ++ #[test] ++ fn test_replace_errno() { ++ let e = Error::Nix { ++ msg: "test".to_string(), ++ source: Errno::ENOENT, ++ }; ++ ++ assert_eq!( ++ Errno::ENOEXEC, ++ e.replace_errno(Errno::ENOENT, Errno::ENOEXEC).get_errno(), ++ ); ++ } + } +-- +2.33.0 + diff --git a/backport-test-device-refactor-and-increase-test-line-coverage.patch b/backport-test-device-refactor-and-increase-test-line-coverage.patch new file mode 100644 index 0000000..4c9ed16 --- /dev/null +++ b/backport-test-device-refactor-and-increase-test-line-coverage.patch @@ -0,0 +1,1606 @@ +From 02b388d6cc30d6bbd11e6ca9d5f5b018ad6638bb Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 8 Nov 2023 02:11:59 +0800 +Subject: [PATCH 047/103] test(device): refactor and increase test line + coverage + +--- + exts/devmaster/src/lib/rules/exec_mgr.rs | 13 +- + exts/devmaster/src/lib/utils/commons.rs | 9 +- + libs/device/src/device.rs | 909 ++++++++++++++++------- + libs/device/src/device_monitor.rs | 4 +- + libs/device/src/error.rs | 30 +- + libs/device/src/utils.rs | 2 + + 6 files changed, 651 insertions(+), 316 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/exec_mgr.rs b/exts/devmaster/src/lib/rules/exec_mgr.rs +index bb8ed012..66c1a06c 100644 +--- a/exts/devmaster/src/lib/rules/exec_mgr.rs ++++ b/exts/devmaster/src/lib/rules/exec_mgr.rs +@@ -150,13 +150,7 @@ impl ExecuteManager { + + // copy all tags to cloned device + for tag in &device.borrow().tag_iter() { +- device_db_clone +- .borrow() +- .add_tag(tag, false) +- .map_err(|e| Error::RulesExecuteError { +- msg: format!("failed to add tag ({})", e), +- errno: e.get_errno(), +- })?; ++ device_db_clone.borrow().add_tag(tag, false); + } + + // add property to cloned device +@@ -1539,10 +1533,7 @@ impl ExecuteManager { + if token.read().unwrap().as_ref().unwrap().op == OperatorType::Remove { + device.borrow().remove_tag(&value); + } else { +- execute_err!( +- token.read().unwrap().as_ref().unwrap(), +- device.borrow().add_tag(&value, true) +- )?; ++ device.borrow().add_tag(&value, true); + } + + Ok(true) +diff --git a/exts/devmaster/src/lib/utils/commons.rs b/exts/devmaster/src/lib/utils/commons.rs +index 5b465710..1162a9ef 100644 +--- a/exts/devmaster/src/lib/utils/commons.rs ++++ b/exts/devmaster/src/lib/utils/commons.rs +@@ -541,14 +541,7 @@ pub(crate) fn initialize_device_usec( + .map_or(0, |v| v.as_secs()) + }); + +- dev_new +- .borrow() +- .set_usec_initialized(timestamp) +- .map_err(|e| { +- log::error!("failed to set initialization timestamp: {}", e); +- e +- }) +- .context(DeviceSnafu)?; ++ dev_new.borrow().set_usec_initialized(timestamp); + + Ok(()) + } +diff --git a/libs/device/src/device.rs b/libs/device/src/device.rs +index d6a4dc07..6673d823 100644 +--- a/libs/device/src/device.rs ++++ b/libs/device/src/device.rs +@@ -41,10 +41,12 @@ use std::path::Path; + use std::rc::Rc; + use std::result::Result; + ++/// default directory to contain runtime temporary files ++pub const DEFAULT_BASE_DIR: &str = "/run/devmaster"; + /// database directory path +-pub const DB_BASE_DIR: &str = "/run/devmaster/data/"; ++pub const DB_BASE_DIR: &str = "data"; + /// tags directory path +-pub const TAGS_BASE_DIR: &str = "/run/devmaster/tags/"; ++pub const TAGS_BASE_DIR: &str = "tags"; + + /// Device + #[derive(Debug, Clone)] +@@ -147,6 +149,9 @@ pub struct Device { + pub sealed: RefCell, + /// persist device db during switching root from initrd + pub db_persist: RefCell, ++ ++ /// the base directory path to contain runtime temporary files ++ pub base_path: RefCell, + } + + impl Default for Device { +@@ -206,9 +211,15 @@ impl Device { + children: RefCell::new(HashMap::new()), + children_enumerated: RefCell::new(false), + sysattrs_cached: RefCell::new(false), ++ base_path: RefCell::new(DEFAULT_BASE_DIR.to_string()), + } + } + ++ /// change db prefix ++ pub fn set_base_path(&self, prefix: &str) { ++ self.base_path.replace(prefix.to_string()); ++ } ++ + /// create Device from buffer + pub fn from_nulstr(nulstr: &[u8]) -> Result { + let device = Device::new(); +@@ -297,8 +308,9 @@ impl Device { + Ok(device) + } + +- /// create a Device instance from path +- /// path falls into two kinds: devname (/dev/...) and syspath (/sys/devices/...) ++ /// Create a Device instance from path. ++ /// ++ /// The path falls into two kinds: devname (/dev/...) and syspath (/sys/devices/...) + pub fn from_path(path: &str) -> Result { + if path.starts_with("/dev") { + return Device::from_devname(path); +@@ -726,9 +738,9 @@ impl Device { + }; + + if !filename.is_empty() { +- self.set_subsystem(&filename)?; ++ self.set_subsystem(&filename); + } else if self.devpath.borrow().starts_with("/module/") { +- self.set_subsystem("module")?; ++ self.set_subsystem("module"); + } else if self.devpath.borrow().contains("/drivers/") + || self.devpath.borrow().contains("/drivers") + { +@@ -736,7 +748,7 @@ impl Device { + } else if self.devpath.borrow().starts_with("/class/") + || self.devpath.borrow().starts_with("/bus/") + { +- self.set_subsystem("subsystem")?; ++ self.set_subsystem("subsystem"); + } else { + self.subsystem_set.replace(true); + } +@@ -828,10 +840,7 @@ impl Device { + }; + + // if the device has no driver, clear it from internal property +- self.set_driver(&driver).map_err(|e| Error::Nix { +- msg: format!("get_driver failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.set_driver(&driver); + } + + if self.driver.borrow().is_empty() { +@@ -846,12 +855,7 @@ impl Device { + + /// get device name + pub fn get_devname(&self) -> Result { +- match self.read_uevent_file() { +- Ok(_) => {} +- Err(e) => { +- return Err(e); +- } +- } ++ self.read_uevent_file()?; + + if self.devname.borrow().is_empty() { + return Err(Error::Nix { +@@ -1364,9 +1368,9 @@ impl Device { + } + + /// add tag to the device object +- pub fn add_tag(&self, tag: &str, both: bool) -> Result<(), Error> { ++ pub fn add_tag(&self, tag: &str, both: bool) { + if tag.trim().is_empty() { +- return Ok(()); ++ return; + } + + self.all_tags.borrow_mut().insert(tag.trim().to_string()); +@@ -1377,16 +1381,13 @@ impl Device { + .insert(tag.trim().to_string()); + } + self.property_tags_outdated.replace(true); +- Ok(()) + } + + /// add a set of tags, separated by ':' +- pub fn add_tags(&self, tags: &str, both: bool) -> Result<(), Error> { ++ pub fn add_tags(&self, tags: &str, both: bool) { + for tag in tags.split(':') { +- self.add_tag(tag, both)?; ++ self.add_tag(tag, both); + } +- +- Ok(()) + } + + /// remove specific tag +@@ -1420,8 +1421,22 @@ impl Device { + Ok(*self.devlink_priority.borrow()) + } + +- /// get the device id +- /// device id is used to identify database file in /run/devmaster/data/ ++ /// Get the device id. ++ /// ++ /// Device id is used to identify database file in /run/devmaster/data/. ++ /// ++ /// The format is like: ++ /// ++ /// character device: c: ++ /// ++ /// block device: b: ++ /// ++ /// network interface: n ++ /// ++ /// drivers: +drivers:: ++ /// ++ /// other subsystems: +: ++ /// + pub fn get_device_id(&self) -> Result { + if self.device_id.borrow().is_empty() { + let subsystem = self.get_subsystem().map_err(|e| Error::Nix { +@@ -1535,25 +1550,25 @@ impl Device { + } + + /// set the initialized timestamp +- pub fn set_usec_initialized(&self, time: u64) -> Result<(), Error> { +- self.add_property_internal("USEC_INITIALIZED", &time.to_string())?; ++ pub fn set_usec_initialized(&self, time: u64) { ++ self.add_property_internal("USEC_INITIALIZED", &time.to_string()) ++ .unwrap(); + self.usec_initialized.replace(time); +- Ok(()) ++ } ++ ++ #[inline] ++ fn cleanup(db: &str, tmp_file: &str) { ++ let _ = unlink(db); ++ let _ = unlink(tmp_file); + } + + /// update device database + pub fn update_db(&self) -> Result<(), Error> { +- #[inline] +- fn cleanup(db: &str, tmp_file: &str) { +- let _ = unlink(db); +- let _ = unlink(tmp_file); +- } +- + let has_info = self.has_info(); + + let id = self.get_device_id()?; + +- let db_path = format!("{}{}", DB_BASE_DIR, id); ++ let db_path = format!("{}/{}/{}", self.base_path.borrow(), DB_BASE_DIR, id); + + if !has_info && *self.devnum.borrow() == 0 && *self.ifindex.borrow() == 0 { + unlink(db_path.as_str()).map_err(|e| Error::Nix { +@@ -1564,14 +1579,19 @@ impl Device { + return Ok(()); + } + +- create_dir_all(DB_BASE_DIR).map_err(|e| Error::Nix { +- msg: "update_db failed: can't create db directory".to_string(), +- source: e +- .raw_os_error() +- .map_or_else(|| nix::Error::EIO, nix::Error::from_i32), ++ create_dir_all(&format!("{}/{}", self.base_path.borrow(), DB_BASE_DIR)).map_err(|e| { ++ Error::Nix { ++ msg: "update_db failed: can't create db directory".to_string(), ++ source: e ++ .raw_os_error() ++ .map_or_else(|| nix::Error::EIO, nix::Error::from_i32), ++ } + })?; + +- if let Err(e) = chmod(DB_BASE_DIR, 0o750) { ++ if let Err(e) = chmod( ++ &format!("{}/{}", self.base_path.borrow(), DB_BASE_DIR), ++ 0o750, ++ ) { + log::error!("Failed to set permission for /run/devmaster/data/: {}", e); + } + +@@ -1586,6 +1606,21 @@ impl Device { + } + })?; + ++ self.atomic_create_db(&mut file, tmp_file.as_str(), db_path.as_str()) ++ .map_err(|e| { ++ Self::cleanup(&db_path, &tmp_file); ++ e ++ })?; ++ ++ Ok(()) ++ } ++ ++ fn atomic_create_db( ++ &self, ++ file: &mut File, ++ tmp_file: &str, ++ db_path: &str, ++ ) -> Result<(), Error> { + fchmod( + file.as_raw_fd(), + if *self.db_persist.borrow() { +@@ -1594,135 +1629,72 @@ impl Device { + Mode::from_bits(0o640).unwrap() + }, + ) +- .map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: "update_db failed: can't change the mode of temporary file".to_string(), +- source: e, +- } ++ .context(Nix { ++ msg: "update_db failed: can't change the mode of temporary file".to_string(), + })?; + +- if has_info { ++ if self.has_info() { + if *self.devnum.borrow() > 0 { + for link in self.devlinks.borrow().iter() { + file.write(format!("S:{}\n", link.strip_prefix("/dev/").unwrap()).as_bytes()) +- .map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: format!( +- "update_db failed: can't write devlink '{}' to db", +- link +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ .context(Io { ++ msg: format!("update_db failed: can't write devlink '{}' to db", link), + })?; + } + + if *self.devlink_priority.borrow() != 0 { + file.write(format!("L:{}\n", self.devlink_priority.borrow()).as_bytes()) +- .map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: format!( +- "update_db failed: can't write devlink priority '{}' to db", +- *self.devlink_priority.borrow() +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ .context(Io { ++ msg: format!( ++ "update_db failed: can't write devlink priority '{}' to db", ++ *self.devlink_priority.borrow() ++ ), + })?; + } + } + + if *self.usec_initialized.borrow() > 0 { + file.write(format!("I:{}\n", self.usec_initialized.borrow()).as_bytes()) +- .map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: format!( +- "update_db failed: can't write initial usec '{}' to db", +- *self.usec_initialized.borrow() +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ .context(Io { ++ msg: format!( ++ "update_db failed: can't write initial usec '{}' to db", ++ *self.usec_initialized.borrow() ++ ), + })?; + } + + for (k, v) in self.properties_db.borrow().iter() { + file.write(format!("E:{}={}\n", k, v).as_bytes()) +- .map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: format!( +- "update_db failed: can't write property '{}'='{}' to db", +- k, v +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ .context(Io { ++ msg: format!( ++ "update_db failed: can't write property '{}'='{}' to db", ++ k, v ++ ), + })?; + } + + for tag in self.all_tags.borrow().iter() { +- file.write(format!("G:{}\n", tag).as_bytes()).map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: "update_db failed: can't write tag '{}' to db".to_string(), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ file.write(format!("G:{}\n", tag).as_bytes()).context(Io { ++ msg: "update_db failed: can't write tag '{}' to db".to_string(), + })?; + } + + for tag in self.current_tags.borrow().iter() { +- file.write(format!("Q:{}\n", tag).as_bytes()).map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: format!( +- "update_db failed: failed to write current tag '{}' to db", +- tag +- ), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ file.write(format!("Q:{}\n", tag).as_bytes()).context(Io { ++ msg: format!( ++ "update_db failed: failed to write current tag '{}' to db", ++ tag ++ ), + })?; + } + } + +- file.flush().map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: "update_db failed: can't flush db".to_string(), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ file.flush().context(Io { ++ msg: "update_db failed: can't flush db".to_string(), + })?; + +- rename(&tmp_file, &db_path).map_err(|e| { +- cleanup(&db_path, &tmp_file); +- Error::Nix { +- msg: "update_db failed: can't rename temporary file".to_string(), +- source: e +- .raw_os_error() +- .map(nix::Error::from_i32) +- .unwrap_or(nix::Error::EIO), +- } ++ rename(tmp_file, &db_path).context(Io { ++ msg: "update_db failed: can't rename temporary file".to_string(), + })?; + + Ok(()) +@@ -1732,7 +1704,13 @@ impl Device { + pub fn update_tag(&self, tag: &str, add: bool) -> Result<(), Error> { + let id = self.get_device_id()?; + +- let tag_path = format!("{}{}/{}", TAGS_BASE_DIR, tag, id); ++ let tag_path = format!( ++ "{}/{}/{}/{}", ++ self.base_path.borrow(), ++ TAGS_BASE_DIR, ++ tag, ++ id ++ ); + + if add { + touch_file(&tag_path, true, Some(0o444), None, None).map_err(|e| Error::Nix { +@@ -1740,14 +1718,24 @@ impl Device { + source: nix::Error::EINVAL, + })?; + +- if let Err(e) = chmod(TAGS_BASE_DIR, 0o750) { +- log::error!("Failed to set permission for {}: {}", TAGS_BASE_DIR, e); ++ if let Err(e) = chmod( ++ &format!("{}/{}", self.base_path.borrow(), TAGS_BASE_DIR), ++ 0o750, ++ ) { ++ log::error!( ++ "Failed to set permission for {}: {}", ++ format!("{}/{}", self.base_path.borrow(), TAGS_BASE_DIR), ++ e ++ ); + } + +- if let Err(e) = chmod(&format!("{}{}", TAGS_BASE_DIR, tag), 0o750) { ++ if let Err(e) = chmod( ++ &format!("{}/{}/{}", self.base_path.borrow(), TAGS_BASE_DIR, tag), ++ 0o750, ++ ) { + log::error!( + "Failed to set permission for {}: {}", +- format!("{}{}", TAGS_BASE_DIR, tag), ++ format!("{}/{}/{}", self.base_path.borrow(), TAGS_BASE_DIR, tag), + e + ); + } +@@ -1794,7 +1782,7 @@ impl Device { + source: e.get_errno(), + })?; + +- let path = format!("{}{}", DB_BASE_DIR, id); ++ let path = format!("{}/{}/{}", self.base_path.borrow(), DB_BASE_DIR, id); + + self.read_db_internal_filename(&path) + .map_err(|e| Error::Nix { +@@ -1917,15 +1905,8 @@ impl Device { + // refuse going down into /sys/fs/cgroup/ or similar places + // where things are not arranged as kobjects in kernel + +- match path.as_os_str().to_str() { +- Some(s) => s.to_string(), +- None => { +- return Err(Error::Nix { +- msg: format!("set_syspath failed: '{:?}' can not change to string", path), +- source: Errno::EINVAL, +- }); +- } +- } ++ /* The path is validated before, thus can directly be unwrapped from os str. */ ++ path.as_os_str().to_str().unwrap().to_string() + } else { + if !path.starts_with("/sys/") { + return Err(Error::Nix { +@@ -1937,15 +1918,8 @@ impl Device { + path.to_string() + }; + +- let devpath = match p.strip_prefix("/sys") { +- Some(p) => p, +- None => { +- return Err(Error::Nix { +- msg: format!("set_syspath failed: '{}' does not start with /sys", p), +- source: Errno::EINVAL, +- }); +- } +- }; ++ /* The syspath is already validated to start with /sys. */ ++ let devpath = p.strip_prefix("/sys").unwrap(); + + if !devpath.starts_with('/') { + return Err(Error::Nix { +@@ -1957,15 +1931,9 @@ impl Device { + }); + } + +- match self.add_property_internal("DEVPATH", devpath) { +- Ok(_) => {} +- Err(e) => { +- return Err(Error::Nix { +- msg: format!("set_syspath failed: {}", e), +- source: Errno::ENODEV, +- }) +- } +- } ++ /* The key 'DEVPATH' is not empty, definitely be ok. */ ++ self.add_property_internal("DEVPATH", devpath).unwrap(); ++ + self.devpath.replace(devpath.to_string()); + self.syspath.replace(p); + +@@ -2074,11 +2042,10 @@ impl Device { + } + + /// set subsystem +- pub fn set_subsystem(&self, subsystem: &str) -> Result<(), Error> { +- self.add_property_internal("SUBSYSTEM", subsystem)?; ++ pub fn set_subsystem(&self, subsystem: &str) { ++ self.add_property_internal("SUBSYSTEM", subsystem).unwrap(); + self.subsystem_set.replace(true); + self.subsystem.replace(subsystem.to_string()); +- Ok(()) + } + + /// set drivers subsystem +@@ -2104,7 +2071,7 @@ impl Device { + }); + } + +- self.set_subsystem("drivers")?; ++ self.set_subsystem("drivers"); + self.driver_subsystem.replace(subsystem); + + Ok(()) +@@ -2120,24 +2087,18 @@ impl Device { + + let mut file = match fs::OpenOptions::new().read(true).open(uevent_file) { + Ok(f) => f, +- Err(e) => match e.raw_os_error() { +- Some(n) => { +- if [libc::EACCES, libc::ENODEV, libc::ENXIO, libc::ENOENT].contains(&n) { +- // the uevent file may be write-only, or the device may be already removed or the device has no uevent file +- return Ok(()); +- } +- return Err(Error::Nix { +- msg: "read_uevent_file failed: can't open uevent file".to_string(), +- source: Errno::from_i32(n), +- }); +- } +- None => { +- return Err(Error::Nix { +- msg: "read_uevent_file failed: can't open uevent file".to_string(), +- source: Errno::EINVAL, +- }); ++ Err(e) => { ++ let n = e.raw_os_error().unwrap_or_default(); ++ ++ if [libc::EACCES, libc::ENODEV, libc::ENXIO, libc::ENOENT].contains(&n) { ++ // the uevent file may be write-only, or the device may be already removed or the device has no uevent file ++ return Ok(()); + } +- }, ++ return Err(Error::Nix { ++ msg: "read_uevent_file failed: can't open uevent file".to_string(), ++ source: Errno::from_i32(n), ++ }); ++ } + }; + + let mut buf = String::new(); +@@ -2177,15 +2138,14 @@ impl Device { + } + + /// set devtype +- pub fn set_devtype(&self, devtype: &str) -> Result<(), Error> { +- self.add_property_internal("DEVTYPE", devtype)?; ++ pub fn set_devtype(&self, devtype: &str) { ++ self.add_property_internal("DEVTYPE", devtype).unwrap(); + self.devtype.replace(devtype.to_string()); +- Ok(()) + } + + /// set ifindex + pub fn set_ifindex(&self, ifindex: &str) -> Result<(), Error> { +- self.add_property_internal("IFINDEX", ifindex)?; ++ self.add_property_internal("IFINDEX", ifindex).unwrap(); + self.ifindex.replace(match ifindex.parse::() { + Ok(idx) => idx, + Err(e) => { +@@ -2199,16 +2159,15 @@ impl Device { + } + + /// set devname +- pub fn set_devname(&self, devname: &str) -> Result<(), Error> { ++ pub fn set_devname(&self, devname: &str) { + let devname = if devname.starts_with('/') { + devname.to_string() + } else { + format!("/dev/{}", devname) + }; + +- self.add_property_internal("DEVNAME", &devname)?; ++ self.add_property_internal("DEVNAME", &devname).unwrap(); + self.devname.replace(devname); +- Ok(()) + } + + /// set devmode +@@ -2223,7 +2182,7 @@ impl Device { + + self.devmode.replace(m); + +- self.add_property_internal("DEVMODE", devmode)?; ++ self.add_property_internal("DEVMODE", devmode).unwrap(); + + Ok(()) + } +@@ -2251,7 +2210,7 @@ impl Device { + + self.devgid.replace(Some(Gid::from_raw(gid))); + +- self.add_property_internal("DEVGID", devgid)?; ++ self.add_property_internal("DEVGID", devgid).unwrap(); + + Ok(()) + } +@@ -2277,8 +2236,8 @@ impl Device { + } + }; + +- self.add_property_internal("MAJOR", major)?; +- self.add_property_internal("MINOR", minor)?; ++ self.add_property_internal("MAJOR", major).unwrap(); ++ self.add_property_internal("MINOR", minor).unwrap(); + self.devnum.replace(makedev(major_num, minor_num)); + + Ok(()) +@@ -2286,7 +2245,7 @@ impl Device { + + /// set diskseq + pub fn set_diskseq(&self, diskseq: &str) -> Result<(), Error> { +- self.add_property_internal("DISKSEQ", diskseq)?; ++ self.add_property_internal("DISKSEQ", diskseq).unwrap(); + + let diskseq_num: u64 = match diskseq.parse() { + Ok(n) => n, +@@ -2304,10 +2263,10 @@ impl Device { + } + + /// set action +- pub fn set_action(&self, action: DeviceAction) -> Result<(), Error> { +- self.add_property_internal("ACTION", &action.to_string())?; ++ pub fn set_action(&self, action: DeviceAction) { ++ self.add_property_internal("ACTION", &action.to_string()) ++ .unwrap(); + self.action.replace(action); +- Ok(()) + } + + /// set action from string +@@ -2325,7 +2284,9 @@ impl Device { + } + }; + +- self.set_action(action) ++ self.set_action(action); ++ ++ Ok(()) + } + + /// set seqnum from string +@@ -2343,22 +2304,22 @@ impl Device { + } + }; + +- self.set_seqnum(seqnum) ++ self.set_seqnum(seqnum); ++ Ok(()) + } + + /// set seqnum +- pub fn set_seqnum(&self, seqnum: u64) -> Result<(), Error> { +- self.add_property_internal("SEQNUM", &seqnum.to_string())?; ++ pub fn set_seqnum(&self, seqnum: u64) { ++ self.add_property_internal("SEQNUM", &seqnum.to_string()) ++ .unwrap(); + self.seqnum.replace(seqnum); +- Ok(()) + } + + /// set driver +- pub fn set_driver(&self, driver: &str) -> Result<(), Error> { +- self.add_property_internal("DRIVER", driver)?; ++ pub fn set_driver(&self, driver: &str) { ++ self.add_property_internal("DRIVER", driver).unwrap(); + self.driver_set.replace(true); + self.driver.replace(driver.to_string()); +- Ok(()) + } + + /// cache sysattr value +@@ -2502,11 +2463,7 @@ impl Device { + }; + + if !devlinks.is_empty() { +- self.add_property_internal("DEVLINKS", &devlinks) +- .map_err(|e| Error::Nix { +- msg: format!("properties_prepare failed: {}", e), +- source: e.get_errno(), +- })?; ++ self.add_property_internal("DEVLINKS", &devlinks).unwrap(); + + self.property_devlinks_outdated.replace(false); + } +@@ -2521,11 +2478,7 @@ impl Device { + + 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), +- source: e.get_errno(), +- })?; ++ self.add_property_internal("TAGS", &all_tags).unwrap(); + } + + let mut current_tags: String = { +@@ -2536,10 +2489,7 @@ impl Device { + if !current_tags.is_empty() { + current_tags.push(':'); + self.add_property_internal("CURRENT_TAGS", ¤t_tags) +- .map_err(|e| Error::Nix { +- msg: format!("properties_prepare failed: {}", e), +- source: e.get_errno(), +- })?; ++ .unwrap(); + } + + self.property_tags_outdated.replace(false); +@@ -2552,29 +2502,19 @@ impl Device { + pub fn read_db_internal_filename(&self, filename: &str) -> Result<(), Error> { + let mut file = match fs::OpenOptions::new().read(true).open(filename) { + Ok(f) => f, +- Err(e) => match e.raw_os_error() { +- Some(n) => { +- if n == libc::ENOENT { +- return Ok(()); +- } +- return Err(Error::Nix { +- msg: format!( +- "read_db_internal_filename failed: can't open db '{}': {}", +- filename, e +- ), +- source: Errno::from_i32(n), +- }); +- } +- None => { +- return Err(Error::Nix { +- msg: format!( +- "read_db_internal_filename failed: can't open db '{}': {}", +- filename, e +- ), +- source: Errno::EINVAL, +- }); ++ Err(e) => { ++ let n = e.raw_os_error().unwrap_or_default(); ++ if n == libc::ENOENT { ++ return Ok(()); + } +- }, ++ return Err(Error::Nix { ++ msg: format!( ++ "read_db_internal_filename failed: can't open db '{}': {}", ++ filename, e ++ ), ++ source: Errno::from_i32(n), ++ }); ++ } + }; + + let mut buf = String::new(); +@@ -2613,17 +2553,10 @@ impl Device { + pub fn handle_db_line(&self, key: &str, value: &str) -> Result<(), Error> { + match key { + "G" | "Q" => { +- self.add_tag(value, key == "Q").map_err(|e| Error::Nix { +- msg: format!("handle_db_line failed: failed to add_tag: {}", e), +- source: e.get_errno(), +- })?; ++ self.add_tag(value, key == "Q"); + } + "S" => { +- self.add_devlink(&format!("/dev/{}", value)) +- .map_err(|e| Error::Nix { +- msg: format!("handle_db_line failed: failed to add_devlink: {}", e), +- source: e.get_errno(), +- })?; ++ self.add_devlink(&format!("/dev/{}", value)).unwrap(); + } + "E" => { + let tokens: Vec<_> = value.split('=').collect(); +@@ -2653,10 +2586,7 @@ impl Device { + source: Errno::EINVAL, + })?; + +- self.set_usec_initialized(time).map_err(|e| Error::Nix { +- msg: format!("handle_db_line failed: {}", e), +- source: Errno::EINVAL, +- })?; ++ self.set_usec_initialized(time); + } + "L" => { + let priority = value.parse::().map_err(|e| Error::Nix { +@@ -2712,10 +2642,7 @@ impl Device { + source: e.get_errno(), + })?; + +- device.set_subsystem(&subsystem).map_err(|e| Error::Nix { +- msg: format!("shallow_clone failed: {}", e), +- source: e.get_errno(), +- })?; ++ device.set_subsystem(&subsystem); + + if subsystem == "drivers" { + device +@@ -2751,11 +2678,11 @@ impl Device { + match key { + "DEVPATH" => self.set_syspath(&format!("/sys{}", value), false)?, + "ACTION" => self.set_action_from_string(value)?, +- "SUBSYSTEM" => self.set_subsystem(value)?, +- "DEVTYPE" => self.set_devtype(value)?, +- "DEVNAME" => self.set_devname(value)?, ++ "SUBSYSTEM" => self.set_subsystem(value), ++ "DEVTYPE" => self.set_devtype(value), ++ "DEVNAME" => self.set_devname(value), + "SEQNUM" => self.set_seqnum_from_string(value)?, +- "DRIVER" => self.set_driver(value)?, ++ "DRIVER" => self.set_driver(value), + "IFINDEX" => self.set_ifindex(value)?, + "USEC_INITIALIZED" => { + self.set_usec_initialized(value.parse::().map_err(|e| Error::Nix { +@@ -2764,14 +2691,14 @@ impl Device { + value, e + ), + source: Errno::EINVAL, +- })?)? ++ })?); + } + "DEVMODE" => self.set_devmode(value)?, + "DEVUID" => self.set_devuid(value)?, + "DEVGID" => self.set_devgid(value)?, + "DISKSEQ" => self.set_diskseq(value)?, + "DEVLINKS" => self.add_devlinks(value)?, +- "TAGS" | "CURRENT_TAGS" => self.add_tags(value, key == "CURRENT_TAGS")?, ++ "TAGS" | "CURRENT_TAGS" => self.add_tags(value, key == "CURRENT_TAGS"), + _ => self.add_property_internal(key, value)?, + } + +@@ -3090,7 +3017,7 @@ impl Device { + log::debug!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ .unwrap_or(self.devpath.borrow().clone()), + e + ) + } +@@ -3106,7 +3033,7 @@ impl Device { + log::error!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ .unwrap_or(self.devpath.borrow().clone()), + e + ) + } +@@ -3122,7 +3049,7 @@ impl Device { + log::debug!( + "failed to read db of '{}': {}", + self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ .unwrap_or(self.devpath.borrow().clone()), + e + ) + } +@@ -3138,7 +3065,7 @@ impl Device { + log::debug!( + "failed to prepare properties of '{}': {}", + self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ .unwrap_or(self.devpath.borrow().clone()), + e + ) + } +@@ -3154,7 +3081,7 @@ impl Device { + log::debug!( + "failed to enumerate children of '{}': {}", + self.get_device_id() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ .unwrap_or(self.devpath.borrow().clone()), + e + ) + } +@@ -3170,8 +3097,7 @@ impl Device { + if let Err(e) = self.read_all_sysattrs() { + log::debug!( + "{}: failed to read all sysattrs: {}", +- self.get_sysname() +- .unwrap_or_else(|_| self.devpath.borrow().clone()), ++ self.get_sysname().unwrap_or(self.devpath.borrow().clone()), + e + ); + } +@@ -3183,15 +3109,30 @@ impl Device { + } + } + ++impl PartialEq for Device { ++ fn eq(&self, other: &Self) -> bool { ++ self.get_syspath().unwrap_or_default() == other.get_syspath().unwrap_or_default() ++ } ++} ++ + #[cfg(test)] + mod tests { ++ use std::panic::catch_unwind; ++ + use crate::{ + device::*, + device_enumerator::{DeviceEnumerationType, DeviceEnumerator}, + utils::LoopDev, + }; ++ use basic::IN_SET; + use libc::S_IFBLK; + ++ fn compare(dev1: &Device, dev2: &Device) -> bool { ++ let syspath_1 = dev1.get_syspath().unwrap(); ++ let syspath_2 = dev2.get_syspath().unwrap(); ++ syspath_1 == syspath_2 ++ } ++ + /// test a single device + fn test_device_one(device: &mut Device) { + let syspath = device.get_syspath().unwrap(); +@@ -3257,7 +3198,14 @@ mod tests { + } + + if device.get_is_initialized().unwrap() { +- // test get_usec_since_initialized: todo ++ match device.get_usec_initialized() { ++ Ok(usec) => { ++ assert!(usec > 0); ++ } ++ Err(e) => { ++ assert_eq!(e.get_errno(), nix::Error::ENODATA); ++ } ++ } + } + + match device.get_property_value("ID_NET_DRIVER") { +@@ -3266,6 +3214,8 @@ mod tests { + assert_eq!(e.get_errno(), Errno::ENOENT); + } + } ++ ++ let _ = device.get_parent_with_subsystem_devtype("usb", Some("usb_interface")); + } + } + Err(e) => { +@@ -3309,6 +3259,9 @@ mod tests { + assert!(basic::error::errno_is_privilege(e.get_errno())); + } + } ++ ++ let dev2 = Device::from_path(&device.get_syspath().unwrap()).unwrap(); ++ assert!(compare(&device_new, &dev2)); + } + Err(e) => { + assert!( +@@ -3399,6 +3352,82 @@ mod tests { + } + } + ++ if let Err(e) = device.get_diskseq() { ++ assert_eq!(e.get_errno(), errno::Errno::ENOENT); ++ } ++ ++ if let Err(e) = device.get_seqnum() { ++ assert_eq!(e.get_errno(), errno::Errno::ENOENT); ++ } ++ ++ if let Err(e) = device.get_trigger_uuid() { ++ assert_eq!(e.get_errno(), nix::errno::Errno::ENOENT); ++ } ++ ++ match device.get_devname() { ++ Ok(devname) => { ++ let st = nix::sys::stat::stat(devname.as_str()).unwrap(); ++ let uid = st.st_uid; ++ let gid = st.st_gid; ++ let mode = st.st_mode; ++ ++ match device.get_devnode_uid() { ++ Ok(dev_uid) => { ++ assert_eq!(uid, dev_uid.as_raw()); ++ } ++ Err(e) => { ++ assert!(IN_SET!(e.get_errno(), nix::errno::Errno::ENOENT)); ++ } ++ } ++ ++ match device.get_devnode_gid() { ++ Ok(dev_gid) => { ++ assert_eq!(gid, dev_gid.as_raw()); ++ } ++ Err(e) => { ++ assert!(IN_SET!(e.get_errno(), nix::errno::Errno::ENOENT)); ++ } ++ } ++ ++ match device.get_devnode_mode() { ++ Ok(dev_mode) => { ++ assert_eq!(mode & 0o777, dev_mode); ++ } ++ Err(e) => { ++ assert!(IN_SET!(e.get_errno(), nix::errno::Errno::ENOENT)); ++ } ++ } ++ } ++ Err(e) => { ++ assert!(IN_SET!(e.get_errno(), nix::errno::Errno::ENOENT)); ++ } ++ } ++ ++ if let Err(e) = device.get_action() { ++ assert_eq!(e.get_errno(), nix::errno::Errno::ENOENT); ++ } ++ ++ let _shadow = device.shallow_clone().unwrap(); ++ let _db = device.clone_with_db().unwrap(); ++ ++ /* Test set and get devlink priority. */ ++ device.set_devlink_priority(10); ++ assert_eq!(10, device.get_devlink_priority().unwrap()); ++ ++ /* Test add devlinks */ ++ device.add_devlinks("/dev/test /dev/test1").unwrap(); ++ ++ assert_eq!( ++ device.add_devlink("/root/test").unwrap_err().get_errno(), ++ nix::Error::EINVAL ++ ); ++ assert_eq!( ++ device.add_devlink("/dev").unwrap_err().get_errno(), ++ nix::Error::EINVAL ++ ); ++ ++ device.update_db().unwrap(); ++ + /* Test enumerating child devices */ + for (subdir, child) in &device.child_iter() { + let canoicalized_path = +@@ -3414,7 +3443,51 @@ mod tests { + let p = format!("{}/{}", syspath, sysattr); + let path = Path::new(&p); + assert!(path.exists()); ++ ++ let st = nix::sys::stat::stat(path).unwrap(); ++ if st.st_mode & S_IWUSR == 0 { ++ assert!(device ++ .set_sysattr_value(sysattr.as_str(), Some("")) ++ .is_err()); ++ } ++ ++ let _value = device.get_sysattr_value(sysattr.as_str()); ++ } ++ ++ /* Test iterators */ ++ for tag in &device.tag_iter() { ++ assert!(device.has_tag(tag.as_str()).unwrap()); ++ } ++ ++ for tag in &device.current_tag_iter() { ++ assert!(device.has_current_tag(tag.as_str()).unwrap()); + } ++ ++ for devlink in &device.devlink_iter() { ++ assert!(device.has_devlink(devlink.as_str())); ++ } ++ ++ for (k, v) in &device.property_iter() { ++ assert_eq!(&device.get_property_value(k.as_str()).unwrap(), v); ++ } ++ ++ device.cleanup_devlinks(); ++ device.cleanup_tags(); ++ ++ let db = device.get_device_id().unwrap(); ++ let _ = unlink(format!("/tmp/devmaster/data/{}", db).as_str()); ++ ++ /* Test open device node. */ ++ let _ = device.open(OFlag::O_RDONLY); ++ let _ = device.open(OFlag::O_WRONLY); ++ let _ = device.open(OFlag::O_RDWR); ++ let _ = device.open(OFlag::O_EXCL); ++ ++ /* Cover exceptional code branches. */ ++ let _ = device.tag_iter(); ++ let _ = device.current_tag_iter(); ++ let _ = device.devlink_iter(); ++ let _ = device.property_iter(); + } + + #[test] +@@ -3422,6 +3495,7 @@ mod tests { + let mut enumerator = DeviceEnumerator::new(); + enumerator.set_enumerator_type(DeviceEnumerationType::All); + for device in enumerator.iter() { ++ device.borrow().set_base_path("/tmp/devmaster"); + test_device_one(&mut device.as_ref().borrow_mut()); + } + } +@@ -3494,12 +3568,15 @@ mod tests { + fn test_device_tag_iterator() { + #[inline] + fn inner_test(dev: &mut Device) -> Result<(), Error> { +- dev.add_tag("test_tag", true).unwrap(); ++ dev.add_tag("test_tag", true); ++ ++ let mut all_tags = HashSet::new(); + + for tag in &dev.tag_iter() { +- assert_eq!(tag, "test_tag"); ++ all_tags.insert(tag.clone()); + } + ++ assert!(all_tags.contains("test_tag")); + Ok(()) + } + +@@ -3596,14 +3673,14 @@ mod tests { + #[inline] + fn inner_test(dev: &mut Device) -> Result<(), Error> { + dev.add_devlinks("test1 test2")?; +- dev.add_tags("tag1:tag2", true)?; ++ dev.add_tags("tag1:tag2", true); + dev.add_property("key", "value")?; + dev.set_devlink_priority(10); +- dev.set_usec_initialized(1000)?; ++ dev.set_usec_initialized(1000); + + dev.update_db()?; + +- let db_path = format!("{}{}", DB_BASE_DIR, dev.get_device_id()?); ++ let db_path = format!("/tmp/devmaster/{}/{}", DB_BASE_DIR, dev.get_device_id()?); + + unlink(db_path.as_str()).unwrap(); + +@@ -3619,9 +3696,14 @@ mod tests { + fn test_update_tag() { + #[inline] + fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ dev.set_usec_initialized(1000); ++ dev.add_tags("test_update_tag:test_update_tag2", true); ++ dev.remove_tag("test_update_tag"); ++ dev.update_db()?; + dev.update_tag("test_update_tag", true)?; ++ dev.update_tag("test_update_tag2", true)?; + let tag_path = format!( +- "/run/devmaster/tags/test_update_tag/{}", ++ "/tmp/devmaster/tags/test_update_tag/{}", + dev.get_device_id()? + ); + assert!(Path::new(tag_path.as_str()).exists()); +@@ -3629,6 +3711,15 @@ mod tests { + dev.update_tag("test_update_tag", false)?; + assert!(!Path::new(tag_path.as_str()).exists()); + ++ let _ = dev.get_usec_initialized().unwrap(); ++ ++ assert!(dev.has_tag("test_update_tag").unwrap()); ++ assert!(dev.has_tag("test_update_tag2").unwrap()); ++ assert!(!dev.has_current_tag("test_update_tag").unwrap()); ++ assert!(dev.has_current_tag("test_update_tag2").unwrap()); ++ ++ dev.cleanup_tags(); ++ + Ok(()) + } + +@@ -3636,4 +3727,250 @@ mod tests { + assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); + } + } ++ ++ #[test] ++ fn test_read_all_sysattrs() { ++ #[inline] ++ fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ dev.read_all_sysattrs().unwrap(); ++ ++ for sysattr in &dev.sysattr_iter() { ++ if let Err(e) = dev.get_sysattr_value(sysattr) { ++ assert!(!IN_SET!(e.get_errno(), Errno::EPERM, Errno::EINVAL)); ++ } ++ } ++ ++ Ok(()) ++ } ++ ++ if let Err(e) = LoopDev::inner_process("/tmp/test_read_all_sysattrs", 1024 * 10, inner_test) ++ { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_enumerate_children() { ++ #[inline] ++ fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ dev.enumerate_children().unwrap(); ++ ++ for _ in &dev.child_iter() {} ++ ++ Ok(()) ++ } ++ ++ if let Err(e) = ++ LoopDev::inner_process("/tmp/test_enumerate_children", 1024 * 10, inner_test) ++ { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_shallow_clone() { ++ #[inline] ++ fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ let s1 = dev.get_syspath().unwrap(); ++ ++ let dev_clone = dev.shallow_clone().unwrap(); ++ ++ assert_eq!(s1, dev_clone.get_syspath().unwrap()); ++ ++ Ok(()) ++ } ++ ++ if let Err(e) = LoopDev::inner_process("/tmp/test_shallow_clone", 1024 * 10, inner_test) { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_add_devlink() { ++ #[inline] ++ fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ let s1 = dev.get_syspath().unwrap(); ++ ++ let dev_clone = dev.shallow_clone().unwrap(); ++ ++ assert_eq!(s1, dev_clone.get_syspath().unwrap()); ++ ++ Ok(()) ++ } ++ ++ if let Err(e) = LoopDev::inner_process("/tmp/test_add_devlink", 1024 * 10, inner_test) { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_from() { ++ #[inline] ++ fn inner_test(dev: &mut Device) -> Result<(), Error> { ++ let syspath = dev.get_syspath().unwrap(); ++ let devnum = dev.get_devnum().unwrap(); ++ let id = dev.get_device_id().unwrap(); ++ let (nulstr, _) = dev.get_properties_nulstr().unwrap(); ++ let devname = dev.get_devname().unwrap(); ++ ++ let dev_new = Device::from_syspath(&syspath, true).unwrap(); ++ assert_eq!(dev, &dev_new); ++ ++ let dev_new = Device::from_device_id(&id).unwrap(); ++ assert_eq!(dev, &dev_new); ++ ++ let dev_new = Device::from_nulstr(nulstr.as_slice()).unwrap(); ++ assert_eq!(dev, &dev_new); ++ ++ let dev_new = Device::from_devnum('b', devnum).unwrap(); ++ assert_eq!(dev, &dev_new); ++ ++ let dev_new = Device::from_devname(&devname).unwrap(); ++ assert_eq!(dev, &dev_new); ++ ++ let dev_new_1 = Device::from_path(&syspath).unwrap(); ++ let dev_new_2 = Device::from_path(&devname).unwrap(); ++ assert_eq!(dev_new_1, dev_new_2); ++ ++ assert_eq!( ++ Device::from_devname(&syspath).unwrap_err().get_errno(), ++ Errno::EINVAL ++ ); ++ ++ Ok(()) ++ } ++ ++ if let Err(e) = LoopDev::inner_process("/tmp/test_from", 1024 * 10, inner_test) { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_set_syspath_error() { ++ let device = Device::new(); ++ ++ assert!(device.set_syspath("", true).is_err()); ++ assert!(device.set_syspath(".././///../.", true).is_err()); ++ assert!(device.set_syspath("/not/exist", true).is_err()); ++ assert!(device.set_syspath("/dev/hello", true).is_err()); ++ assert!(device.set_syspath("/sys/devices/none", true).is_err()); ++ assert!(device.set_syspath("/sys/none", true).is_err()); ++ assert_eq!( ++ device.set_syspath("/sys/", true).unwrap_err().get_errno(), ++ nix::Error::ENODEV ++ ); ++ ++ assert_eq!( ++ device ++ .set_syspath("/dev/hello", false) ++ .unwrap_err() ++ .get_errno(), ++ nix::Error::EINVAL ++ ); ++ assert!(device.set_syspath("/sys/", false).is_ok()); ++ assert!(device.set_syspath("/sys", false).is_err()); ++ } ++ ++ #[test] ++ fn test_from_ifindex_error() { ++ assert!(Device::from_ifindex(10000).is_err()); ++ } ++ ++ #[test] ++ fn test_set_seqnum_from_string() { ++ let device = Device::new(); ++ device.set_seqnum_from_string("1000").unwrap(); ++ ++ assert!(device.set_seqnum_from_string("xxxx").is_err()); ++ } ++ ++ #[test] ++ fn test_set_db_persist() { ++ let device = Device::new(); ++ device.set_db_persist(); ++ } ++ ++ #[test] ++ fn test_from_db() { ++ /* Legal db content. */ ++ { ++ let content = "S:disk/by-path/pci-0000:00:10.0-scsi-0:0:0:0-part1 ++I:1698916066 ++E:ID_PART_ENTRY_OFFSET=2048 ++G:devmaster ++Q:devmaster ++V:100 ++"; ++ touch_file("/tmp/tmp_db", false, None, None, None).unwrap(); ++ let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write(content.as_bytes()).unwrap(); ++ let device = Device::new(); ++ device.read_db_internal_filename("/tmp/tmp_db").unwrap(); ++ } ++ ++ /* Strange db entry would be ignored. */ ++ { ++ let content = "error ++"; ++ let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write(content.as_bytes()).unwrap(); ++ let device = Device::new(); ++ device.read_db_internal_filename("/tmp/tmp_db").unwrap(); ++ } ++ ++ /* Illegal db entry value would throw error. */ ++ { ++ let content = "I:invalid ++"; ++ let mut f = File::options().write(true).open("/tmp/tmp_db").unwrap(); ++ f.write(content.as_bytes()).unwrap(); ++ let device = Device::new(); ++ assert!(device.read_db_internal_filename("/tmp/tmp_db").is_err()); ++ } ++ ++ /* DB shoud be readable. */ ++ { ++ touch_file("/tmp/tmp_db_writeonly", false, Some(0o222), None, None).unwrap(); ++ let device = Device::new(); ++ assert!(device.read_db_internal_filename("/tmp/tmp_db").is_err()); ++ } ++ ++ /* Test different kinds of illegal db entry. */ ++ { ++ let device = Device::new(); ++ assert!(device ++ .amend_key_value("USEC_INITIALIZED", "invalid") ++ .is_err()); ++ assert!(device.handle_db_line("E", "ID_TEST==invalid").is_err()); ++ assert!(device.handle_db_line("E", "=invalid").is_err()); ++ assert!(device.handle_db_line("I", "invalid").is_err()); ++ assert!(device.handle_db_line("L", "invalid").is_err()); ++ assert!(device.handle_db_line("W", "").is_ok()); ++ assert!(device.handle_db_line("V", "invalid").is_err()); ++ } ++ ++ unlink("/tmp/tmp_db").unwrap(); ++ } ++ ++ #[test] ++ fn test_set_is_initialized() { ++ let device = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ device.set_is_initialized(); ++ device ++ .trigger_with_uuid(DeviceAction::Change, false) ++ .unwrap(); ++ device ++ .trigger_with_uuid(DeviceAction::Change, true) ++ .unwrap(); ++ device.trigger(DeviceAction::Change).unwrap(); ++ } ++ ++ #[test] ++ fn test_get_usec_since_initialized() { ++ assert!(catch_unwind(|| { ++ let dev = Device::new(); ++ dev.get_usec_since_initialized().unwrap(); ++ }) ++ .is_err()); ++ } + } +diff --git a/libs/device/src/device_monitor.rs b/libs/device/src/device_monitor.rs +index 0c9bcba1..729a7dcb 100644 +--- a/libs/device/src/device_monitor.rs ++++ b/libs/device/src/device_monitor.rs +@@ -299,8 +299,8 @@ mod tests { + spawn(|| { + let device = Device::from_devname("/dev/sda").unwrap(); + device.set_action_from_string("change").unwrap(); +- device.set_subsystem("block").unwrap(); +- device.set_seqnum(1000).unwrap(); ++ device.set_subsystem("block"); ++ device.set_seqnum(1000); + + let broadcaster = DeviceMonitor::new(MonitorNetlinkGroup::None, None); + broadcaster.send_device(&device, None).unwrap(); +diff --git a/libs/device/src/error.rs b/libs/device/src/error.rs +index 90f67d9d..40589c21 100644 +--- a/libs/device/src/error.rs ++++ b/libs/device/src/error.rs +@@ -28,16 +28,31 @@ pub enum Error { + /// errno indicates the error kind + source: nix::Error, + }, ++ ++ #[snafu(context, display("IO error: {}", msg))] ++ Io { ++ /// message ++ msg: String, ++ source: std::io::Error, ++ }, ++ ++ #[snafu(context, display("Basic error: {}", msg))] ++ Basic { msg: String, source: basic::Error }, + } + + impl Error { + /// extract the errno from error + pub fn get_errno(&self) -> Errno { + match self { +- Error::Nix { ++ Self::Nix { + msg: _, + source: errno, + } => *errno, ++ Self::Io { ++ msg: _, ++ source: errno, ++ } => Errno::from_i32(errno.raw_os_error().unwrap_or_default()), ++ Self::Basic { msg: _, source } => Errno::from_i32(source.get_errno()), + } + } + } +@@ -56,17 +71,14 @@ macro_rules! err_wrapper { + impl Error { + /// check whether the device error belongs to specific errno + pub fn is_errno(&self, errno: nix::Error) -> bool { +- match self { +- Self::Nix { msg: _, source } => *source == errno, +- } ++ self.get_errno() == errno + } + + /// check whether the device error indicates the device is absent + pub fn is_absent(&self) -> bool { +- match self { +- Self::Nix { msg: _, source } => { +- matches!(source, Errno::ENODEV | Errno::ENXIO | Errno::ENOENT) +- } +- } ++ matches!( ++ self.get_errno(), ++ Errno::ENODEV | Errno::ENXIO | Errno::ENOENT ++ ) + } + } +diff --git a/libs/device/src/utils.rs b/libs/device/src/utils.rs +index 9d768b38..df750edf 100644 +--- a/libs/device/src/utils.rs ++++ b/libs/device/src/utils.rs +@@ -188,6 +188,8 @@ impl LoopDev { + source: nix::Error::EINVAL, + })?)?; + ++ dev.set_base_path("/tmp/devmaster"); ++ + f(&mut dev) + } + } +-- +2.33.0 + diff --git a/backport-test-devmaster-add-UT-cases-for-rules-load.patch b/backport-test-devmaster-add-UT-cases-for-rules-load.patch new file mode 100644 index 0000000..159814f --- /dev/null +++ b/backport-test-devmaster-add-UT-cases-for-rules-load.patch @@ -0,0 +1,410 @@ +From 2d38b2906a0b71c1ea591d83fba9879ce9fb6e65 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 17 Nov 2023 06:01:41 +0800 +Subject: [PATCH 072/103] test(devmaster): add UT cases for rules load + +--- + exts/devmaster/src/lib/rules/rules_load.rs | 342 +++++++++++++++++---- + 1 file changed, 287 insertions(+), 55 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index 57ff19ac..e9dd2259 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -1725,7 +1725,6 @@ impl RuleToken { + + #[cfg(test)] + mod tests { +- use crate::config::*; + use basic::fs_util::touch_file; + use log::init_log; + use log::Level; +@@ -1733,32 +1732,245 @@ mod tests { + use super::*; + use std::fs::create_dir_all; + use std::fs::remove_dir_all; +- use std::{fs, path::Path, thread::JoinHandle}; +- +- fn create_test_rules_dir(dir: &'static str) { +- assert!(fs::create_dir(dir).is_ok()); +- assert!(fs::write( +- format!("{}/test.rules", dir), +- "ACTION == \"change\", SYMLINK += \"test1\" +-ACTION == \"change\", SYMLINK += \"test11\", \\ +-SYMLINK += \"test111\" +-ACTION == \"change\", SYMLINK += \"test1111\", \\ +-SYMLINK += \"test11111\", \\ +-SYMLINK += \"test111111\"", +- ) +- .is_ok()); ++ use std::io::Write; ++ use std::panic::catch_unwind; ++ use std::{fs, path::Path}; ++ ++ fn create_tmp_rules(dir: &'static str, file: &str, content: &str) { ++ assert!(fs::create_dir_all(dir).is_ok()); ++ assert!(fs::write(format!("{}/{}", dir, file), content,).is_ok()); + } + +- fn clear_test_rules_dir(dir: &'static str) { ++ fn clear_tmp_rules(dir: &'static str) { + if Path::new(dir).exists() { + assert!(fs::remove_dir_all(dir).is_ok()); + } + } + + #[test] +- fn test_rules_new() { ++ fn test_load_rules() { ++ init_log( ++ "test_load_rules", ++ Level::Debug, ++ vec!["console"], ++ "", ++ 0, ++ 0, ++ false, ++ ); ++ clear_tmp_rules("/tmp/devmaster/rules"); ++ ++ let legal_rule = vec![ ++ "ACTION == \"change\", SYMLINK += \"test1\"", // Test legal rules. ++ "ACTION == \"change\", SYMLINK += \"test11\", \\ ++ SYMLINK += \"test111\"", // Test double line tying. ++ "ACTION == \"change\", SYMLINK += \"test1111\", \\ ++ SYMLINK += \"test11111\", \\ ++ SYMLINK += \"test111111\"", // Test triple line tying. ++ "SYMLINK == \"$hello\"", // Illegal placeholder will throw warning rather than panic. ++ "NAME += \"xxx\"", // NAME will transfer operator += to =. ++ "NAME == \"$hello\"", // Illegal placeholder will throw warning rather than panic. ++ "ENV{xxx}:=\"xxx\"", // ENV will transfer final assignment := to =. ++ "ENV{xxx}=\"$hello\"", // Illegal placeholder will throw warning rather than panic. ++ "CONST{arch}==\"x86_64\"", // Test legal CONST usage. ++ "CONST{virt}==\"qemu\"", // Test legal CONST usage. ++ "SUBSYSTEM==\"bus\"", // SUBSYSTEM will throw warning if the value is 'bus' or 'class'. ++ "DRIVER==\"xxx\"", // Test DRIVER usage. ++ /* ATTR will throw warning if the operator is += or :=, ++ * and transfer the operator into =. ++ */ ++ "ATTR{xxx}+=\"xxx\"", ++ "ATTR{xxx}:=\"xxx\"", ++ "ATTR{xxx}=\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ /* Test SYSCTL usage. */ ++ "SYSCTL{hello}=\"world\"", ++ "SYSCTL{hello}==\"world\"", ++ /* SYSCTL will transfer the += and := operator to =, and trow warning. */ ++ "SYSCTL{hello}+=\"world\"", ++ "SYSCTL{hello}:=\"world\"", ++ "SYSCTL{hello}=\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ "ATTRS{device/xxx}==\"xxx\"", // The attribute with prefix of 'device/' will trow warning. ++ "ATTRS{../xxx}==\"xxx\"", // The attribute with prefix of 'device/' will throw warning. ++ "TAGS==\"xxx\"", // Test TAGS usage. ++ "TEST{777}==\"xx\"", // Test TEST usage. ++ "TEST{777}==\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ "PROGRAM==\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ "IMPORT{program}==\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ "IMPORT{file}=\"x\"", // IMPORT will throw warning if the operator is not matching or unmatching and transfer it to ==. ++ "IMPORT{program}==\"path_id $kernel\"", // If the program is a built-in command, IMPORT will identify it. ++ /* Test OPTIONS usages. */ ++ "OPTIONS+=\"string_escape=none\"", ++ "OPTIONS+=\"db_persist\"", ++ "OPTIONS+=\"log_level=rest\"", ++ "OPTIONS+=\"log_level=10\"", ++ "OWNER+=\"0\"", // OWNER will transfer += to =, and trow a warning. ++ "GROUP+=\"0\"", // GROUP will transfer += to =, and trow a warning. ++ "MODE+=\"777\"", // MODE will transfer += to =, and trow a warning. ++ "MODE=\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ "SECLABEL{x}:=\"$hello\"", // Illegal placeholder in value will throw warning rather than panic. ++ ]; ++ ++ create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); ++ ++ for &content in legal_rule.iter() { ++ let mut f = fs::OpenOptions::new() ++ .write(true) ++ .truncate(true) ++ .open("/tmp/devmaster/rules/00-test.rules") ++ .unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); ++ ++ let _ = Rules::load_rules( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Early, ++ ); ++ } ++ ++ clear_tmp_rules("/tmp/devmaster/rules"); ++ } ++ ++ #[test] ++ fn test_load_rules_panic() { ++ init_log( ++ "test_load_rules_panic", ++ Level::Debug, ++ vec!["console"], ++ "", ++ 0, ++ 0, ++ false, ++ ); ++ clear_tmp_rules("/tmp/devmaster/rules"); ++ ++ let illegal_rule = vec![ ++ "action==\"change\"", // Error in State::Pre ++ "ACtion==\"change\"", // Error in State::Key ++ "ENV{!}==\"hello\"", // Error in State::Attribute ++ "ACTION #= \"hello\"", // Error in State::PreOp ++ "ACTION =# \"hello\"", // Error in State::Op ++ "ACTION == hello", // Error in State::PostOp ++ "ACTION == \"change\"x", // Error in State::PostValue ++ "ACTION = \"change\"", // ACTION can not take assign operator. ++ "DEVPATH{xxx} == \"xxx\"", // DEVPATH can not take attribute. ++ "DEVPATH = \"xxx\"", // DEVPATH can no take assign operator. ++ "KERNEL{xxx} == \"xxx\"", // KERNEL can not take attribute. ++ "KERNEL = \"xxx\"", // KERNEL can not take assign operator. ++ "SYMLINK{xxx} = \"hello\"", // SYMLINK can not take attribute. ++ "NAME{xxx} = \"xxx\"", // NAME can not take attribute. ++ "NAME -= \"xxx\"", // NAME can not take removal operator. ++ "NAME=\"%k\"", // NAME can not take '%k' value. ++ "NAME=\"\"", // NAME can not take empty value. ++ "ENV=\"xxx\"", // ENV must take attribute. ++ "ENV{xxx}-=\"xxx\"", // ENV can not take removal operator. ++ /* ENV with non-match operator can not take the following attributes: ++ * "ACTION" ++ * "DEVLINKS" ++ * "DEVNAME" ++ * "DEVTYPE" ++ * "DRIVER" ++ * "IFINDEX" ++ * "MAJOR" ++ * "MINOR" ++ * "SEQNUM" ++ * "SUBSYSTEM" ++ * "TAGS" ++ */ ++ "ENV{ACTION}=\"xxx\"", ++ "ENV{DEVLINKS}=\"xxx\"", ++ "ENV{DEVNAME}=\"xxx\"", ++ "ENV{DEVTYPE}=\"xxx\"", ++ "ENV{DRIVER}=\"xxx\"", ++ "ENV{IFINDEX}=\"xxx\"", ++ "ENV{MAJOR}=\"xxx\"", ++ "ENV{MINOR}=\"xxx\"", ++ "ENV{SEQNUM}=\"xxx\"", ++ "ENV{SUBSYSTEM}=\"xxx\"", ++ "ENV{TAGS}=\"xxx\"", ++ "CONST==\"xxx\"", // CONST must take an attribute. ++ "CONST{xxx}==\"xxx\"", // CONST can only take "arch" or "virt" attribute. ++ "CONST{virt}=\"qemu\"", // CONST can not take assignment operator. ++ "TAG{xxx}+=\"xxx\"", // TAG can not take attribute. ++ "SUBSYSTEM{xxx}==\"block\"", // SUBSYSTEM can not take attribute. ++ "SUBSYSTEM=\"block\"", // SUBSYSTEM can only take matching or unmatching operators. ++ "DRIVER{xxx}==\"xxx\"", // DRIVER can not take attribute. ++ "DRIVER=\"xxx\"", // DRIVER can only take matching or unmatching operators. ++ "ATTR{$hello}==\"xxx\"", // ATTR can not take illegal attribute. ++ "ATTR{hello}-=\"xxx\"", // ATTR can not take removal operator. ++ /* SYSCTL must take attribute. */ ++ "SYSCTL=\"xxx\"", ++ "SYSCTL==\"xxx\"", ++ "SYSCTL{xxx}-=\"xxx\"", // SYSCTL can not take removal operator. ++ "KERNELS{xxx}==\"xxx\"", // KERNELS can not take attribute. ++ "KERNELS=\"xxx\"", // KERNELS can only take matching or unmatching operators. ++ "SUBSYSTEMS{xxx}==\"xxx\"", // SUBSYSTEMS can not take attribute. ++ "SUBSYSTEMS=\"xxx\"", // SUBSYSTEMS can not take assignment operators. ++ "DRIVERS{xxx}=\"xxx\"", // DRIVERS can not take attribute. ++ "DRIVERS=\"xxx\"", // DRIVERS can not take assignment operators. ++ "ATTRS==\"xxx\"", // ATTRS must take an attribute. ++ "ATTRS{xxx}=\"x\"", // ATTRS can not take assignment operators. ++ "TAGS{xxx}=\"xxx\"", // TAGS can not take attribute. ++ "TAGS=\"xxx\"", // TAGS can not take assignment operators. ++ "TEST{777}=\"x\"", // TEST can not take assignment operators. ++ "PROGRAM{x}==\"x\"", // PROGRAM can not take attribute. ++ "PROGRAM-=\"x\"", // PROGRAM can not take removal attribute. ++ "IMPORT==\"x\"", // IMPORT must take an attribute. ++ "IMPORT{builtin}==\"xxx $kernel\"", // IMPORT{builtin} will panic if the command is not a valid built-in. ++ "IMPORT{x}==\"x\"", // IMPORT will panic if the attribute is invalid. ++ "RESULT{x}==\"x\"", // RESULT can not take attribute. ++ "RESULT{x}=\"x\"", // RESULT can only take matching or unmatching operator. ++ "OPTIONS{x}+=\"x\"", // OPTIONS can not take attribute. ++ "OPTIONS{x}==\"x\"", // OPTIONS can not take matching or unmatching operator. ++ "OPTIONS{x}-=\"x\"", // OPTIONS can not take removal operator. ++ "OPTIONS+=\"link_priority=x\"", // Invalid number of link priority. ++ "OPTIONS+=\"log_level=xxx\"", // Invalid log_level. ++ "OWNER{x}==\"x\"", // OWNER can not take attribute. ++ "OWNER==\"0\"", // OWNER can not take matching or unmatching operator. ++ "OWNER-=\"0\"", // OWNER can not take removal operator. ++ "GROUP==\"0\"", // OWNER can not take matching or unmatching operator. ++ "GROUP-=\"0\"", // OWNER can not take removal operator. ++ "MODE{x}=\"777\"", // MODE can not take attribute. ++ "MODE==\"777\"", // MODE can not take matching or unmatching operator. ++ "MODE-=\"777\"", // MODE can not take removal operator. ++ "SECLABEL=\"xxx\"", // SECLABEL must take an attribute. ++ "SECLABEL{x}==\"x\"", // SECLABEL can not take matching or unmatching operator. ++ "SECLABEL{x}-=\"x\"", // SECLABEL can not take removal operator. ++ "RUN==\"xxx\"", // RUN can not take matching or unmatching operator. ++ "RUN-=\"xxx\"", // RUN can not take removal operator. ++ "RUN{builtin}==\"xxx\"", // RUN will panic if the builtin is invalid. ++ "RUN{xxx}==\"xxx\"", // RUN will panic if the attribute is not builtin or program. ++ "GOTO{xx}=\"xx\"", // GOTO can not take attribute. ++ "GOTO==\"xx\"", // GOTO can only take assignment operator. ++ "LABEL{x}==\"x\"", // LABEL can not take attribute. ++ "LABEL==\"x\"", // LABEL can only take assignment operator. ++ "XXX=\"xxx\"", // Invalid token key. ++ ]; ++ ++ create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); ++ ++ for content in illegal_rule.iter() { ++ let mut f = fs::OpenOptions::new() ++ .write(true) ++ .truncate(true) ++ .open("/tmp/devmaster/rules/00-test.rules") ++ .unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); ++ ++ assert!(catch_unwind(|| { ++ let _ = Rules::load_rules( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Early, ++ ); ++ }) ++ .is_err()); ++ } ++ ++ clear_tmp_rules("/tmp/devmaster/rules"); ++ } ++ ++ #[test] ++ fn test_resolve_name_time() { + init_log( +- "test_rules_new", ++ "test_load_rules", + Level::Debug, + vec!["console"], + "", +@@ -1766,11 +1978,50 @@ SYMLINK += \"test111111\"", + 0, + false, + ); +- clear_test_rules_dir("test_rules_new"); +- create_test_rules_dir("test_rules_new"); +- let rules = Rules::load_rules(DEFAULT_RULES_DIRS.to_vec(), ResolveNameTime::Early); +- println!("{}", rules.read().unwrap()); +- clear_test_rules_dir("test_rules_new"); ++ clear_tmp_rules("/tmp/devmaster/rules"); ++ ++ let legal = vec!["OWNER=\"root\"", "GROUP=\"root\""]; ++ let illegal = vec!["OWNER=\"xxxx\"", "GROUP=\"xxxx\""]; ++ ++ create_tmp_rules("/tmp/devmaster/rules", "00-test.rules", ""); ++ ++ for &content in legal.iter() { ++ let mut f = fs::OpenOptions::new() ++ .write(true) ++ .truncate(true) ++ .open("/tmp/devmaster/rules/00-test.rules") ++ .unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); ++ ++ let _ = Rules::load_rules( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Early, ++ ); ++ } ++ ++ for &content in illegal.iter() { ++ let mut f = fs::OpenOptions::new() ++ .write(true) ++ .truncate(true) ++ .open("/tmp/devmaster/rules/00-test.rules") ++ .unwrap(); ++ f.write_all(content.as_bytes()).unwrap(); ++ ++ let _ = Rules::load_rules( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Late, ++ ); ++ ++ assert!(catch_unwind(|| { ++ let _ = Rules::load_rules( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Early, ++ ); ++ }) ++ .is_err()); ++ } ++ ++ clear_tmp_rules("/tmp/devmaster/rules"); + } + + #[test] +@@ -1955,34 +2206,6 @@ SYMLINK += \"test111111\"", + println!("{:?}", t); + } + +- #[test] +- fn test_rules_share_among_threads() { +- create_test_rules_dir("test_rules_share_among_threads"); +- let rules = Rules::new( +- vec![ +- "test_rules_new_1".to_string(), +- "test_rules_new_2".to_string(), +- ], +- ResolveNameTime::Early, +- ); +- let mut handles = Vec::>::new(); +- (0..5).for_each(|i| { +- let rules_clone = rules.clone(); +- let handle = std::thread::spawn(move || { +- println!("thread {}", i); +- println!("{}", rules_clone); +- }); +- +- handles.push(handle); +- }); +- +- for thread in handles { +- thread.join().unwrap(); +- } +- +- clear_test_rules_dir("test_rules_share_among_threads"); +- } +- + #[test] + fn test_resolve_user_group() { + let mut rules = Rules::new(vec![], ResolveNameTime::Early); +@@ -2124,18 +2347,27 @@ SYMLINK += \"test111111\"", + touch_file( + "/tmp/devmaster/rules/02-c.rules", + false, +- Some(0o222), ++ Some(0o000), + None, + None, + ) + .unwrap(); + +- let rules = Rules::new( ++ let rules = Arc::new(RwLock::new(Rules::new( + vec!["/tmp/devmaster/rules".to_string()], + ResolveNameTime::Never, +- ); ++ ))); ++ ++ // Rules::parse_rules(Arc::new(RwLock::new(rules))); + +- Rules::parse_rules(Arc::new(RwLock::new(rules))); ++ RuleFile::load_file("/tmp/devmaster/rules/00-a.rules".to_string(), rules.clone()); ++ ++ if nix::unistd::getuid().as_raw() != 0 { ++ assert!(catch_unwind(|| { ++ RuleFile::load_file("/tmp/devmaster/rules/02-c.rules".to_string(), rules.clone()); ++ }) ++ .is_err()); ++ } + + remove_dir_all("/tmp/devmaster").unwrap(); + } +-- +2.33.0 + diff --git a/backport-test-devmaster-add-UT-for-execute-unit.patch b/backport-test-devmaster-add-UT-for-execute-unit.patch new file mode 100644 index 0000000..855dd5f --- /dev/null +++ b/backport-test-devmaster-add-UT-for-execute-unit.patch @@ -0,0 +1,275 @@ +From ac6f7187a7e696e1a90210cbcfb6c649385616ac Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 23 Nov 2023 20:17:49 +0800 +Subject: [PATCH 084/103] test(devmaster): add UT for execute unit + +--- + exts/devmaster/src/lib/rules/exec_unit.rs | 255 ++++++++++++++++++++++ + 1 file changed, 255 insertions(+) + +diff --git a/exts/devmaster/src/lib/rules/exec_unit.rs b/exts/devmaster/src/lib/rules/exec_unit.rs +index 828fe1af..93684659 100644 +--- a/exts/devmaster/src/lib/rules/exec_unit.rs ++++ b/exts/devmaster/src/lib/rules/exec_unit.rs +@@ -987,3 +987,258 @@ fn get_subst_type( + *idx = idx_b; + Ok(Some((subst, attr))) + } ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ use crate::rules::rules_load::tests::create_tmp_file; ++ use device::utils::*; ++ use nix::sys::stat::{major, minor}; ++ use std::cell::RefCell; ++ use std::fs::remove_dir_all; ++ use std::path::Path; ++ use std::rc::Rc; ++ ++ #[test] ++ fn test_update_devnode() { ++ if let Err(e) = LoopDev::inner_process( ++ "/tmp/test_update_devnode_tmpfile", ++ 1024 * 1024 * 10, ++ |dev| { ++ let dev = Rc::new(RefCell::new(dev.shallow_clone().unwrap())); ++ let id = dev.borrow().get_device_id().unwrap(); ++ let devnum = dev.borrow().get_devnum().unwrap(); ++ let major_minor = format!("{}:{}", major(devnum), minor(devnum)); ++ ++ create_tmp_file("/tmp/test_update_devnode/data", &id, "", true); ++ ++ dev.borrow().sealed.replace(true); ++ dev.borrow().set_base_path("/tmp/test_update_devnode"); ++ dev.borrow().set_devgid("1").unwrap(); ++ dev.borrow().set_devuid("1").unwrap(); ++ dev.borrow().set_devmode("666").unwrap(); ++ dev.borrow().add_devlink("test_update_devnode/bbb").unwrap(); ++ ++ let mut unit = ExecuteUnitData::new(dev.clone()); ++ unit.clone_device_db().unwrap(); ++ ++ unit.update_devnode(&HashMap::new()).unwrap(); ++ /* Record the devlink in db */ ++ dev.borrow().update_db().unwrap(); ++ ++ let p = Path::new("/dev/test_update_devnode/bbb"); ++ let p_block_s = format!("/dev/block/{}", major_minor); ++ let prior_p_s = format!("/run/devmaster/links/test_update_devnode\\x2fbbb/{}", id); ++ let prior_p = Path::new(&prior_p_s); ++ assert!(p.exists()); ++ assert!(Path::new(&p_block_s).exists()); ++ assert!(p ++ .canonicalize() ++ .unwrap() ++ .ends_with(&dev.borrow().get_sysname().unwrap())); ++ let _ = prior_p.symlink_metadata().unwrap(); // Test symlink exists. ++ ++ let new_dev = Rc::new(RefCell::new(Device::from_device_id(&id).unwrap())); ++ new_dev.borrow().sealed.replace(true); ++ new_dev.borrow().set_base_path("/tmp/test_update_devnode"); ++ new_dev.borrow().set_devgid("0").unwrap(); ++ new_dev.borrow().set_devuid("0").unwrap(); ++ new_dev.borrow().set_devmode("600").unwrap(); ++ ++ let mut unit = ExecuteUnitData::new(new_dev); ++ /* See the devlink in db, but it is absent in the current device object. ++ * ++ * Then update_devnode method will remove the devlink. ++ */ ++ unit.clone_device_db().unwrap(); ++ unit.update_devnode(&HashMap::new()).unwrap(); ++ ++ assert!(!Path::new("/dev/test_update_devnode/bbb").exists()); ++ assert!(Path::new(&p_block_s).exists()); ++ let _ = prior_p.symlink_metadata().unwrap_err(); // Test symlink does not exists. ++ ++ remove_dir_all("/tmp/test_update_devnode").unwrap(); ++ ++ /* Non-block devices do not have device nodes, thus update_devnode method will do nothing. */ ++ let lo = Rc::new(RefCell::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ )); ++ ++ let mut unit = ExecuteUnitData::new(lo); ++ unit.update_devnode(&HashMap::new()).unwrap(); ++ ++ /* Cover error paths when uid, gid or mode is not set. */ ++ let dev = Rc::new(RefCell::new(Device::from_device_id(&id).unwrap())); ++ dev.borrow().sealed.replace(true); ++ dev.borrow().add_devlink("test_update_devnode/xxx").unwrap(); ++ ++ let mut unit = ExecuteUnitData::new(dev.clone()); ++ unit.clone_device_db().unwrap(); ++ unit.update_devnode(&HashMap::new()).unwrap(); ++ ++ let p = Path::new("/dev/test_update_devnode/xxx"); ++ let prior_p_s = format!("/run/devmaster/links/test_update_devnode\\x2fxxx/{}", id); ++ assert!(p.exists()); ++ assert!(Path::new(&p_block_s).exists()); ++ assert!(p ++ .canonicalize() ++ .unwrap() ++ .ends_with(&dev.borrow().get_sysname().unwrap())); ++ let _ = Path::new(&prior_p_s).symlink_metadata().unwrap(); // Test symlink exists. ++ ++ cleanup_node(dev).unwrap(); ++ ++ assert!(!p.exists()); ++ assert!(!Path::new(&p_block_s).exists()); ++ let _ = Path::new(&prior_p_s).symlink_metadata().unwrap_err(); // Test symlink exists. ++ ++ Ok(()) ++ }, ++ ) { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_subst_format() { ++ if let Err(e) = ++ LoopDev::inner_process("/tmp/test_subst_format_tmpfile", 1024 * 1024 * 10, |dev| { ++ let dev = Rc::new(RefCell::new(dev.shallow_clone().unwrap())); ++ let mut unit = ExecuteUnitData::new(dev.clone()); ++ let devnum = dev.borrow().get_devnum().unwrap(); ++ let major_minor = format!("{}:{}", major(devnum), minor(devnum)); ++ let sysname = dev.borrow().get_sysname().unwrap(); ++ ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Attr, Some("dev".to_string())) ++ .unwrap(), ++ major_minor ++ ); ++ ++ assert_eq!( ++ unit.subst_format( ++ FormatSubstitutionType::Attr, ++ Some(format!("[block/{}]dev", sysname)) ++ ) ++ .unwrap(), ++ major_minor ++ ); ++ ++ unit.set_parent(Some(Rc::new(RefCell::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ )))); ++ ++ /* Get the sysattr of parent device set in unit. */ ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Attr, Some("ifindex".to_string())) ++ .unwrap(), ++ "1".to_string() ++ ); ++ ++ /* Invalid sysattr will be replaced with empty string. */ ++ assert!(unit ++ .subst_format( ++ FormatSubstitutionType::Attr, ++ Some("asdfasdfads".to_string()) ++ ) ++ .unwrap() ++ .is_empty()); ++ ++ dev.borrow().add_property("hello", "world").unwrap(); ++ ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Env, Some("hello".to_string())) ++ .unwrap(), ++ "world".to_string() ++ ); ++ ++ assert!(unit ++ .subst_format(FormatSubstitutionType::Env, Some("asdfgasd".to_string())) ++ .unwrap() ++ .is_empty()); ++ ++ assert!(unit ++ .subst_format(FormatSubstitutionType::Driver, None) ++ .unwrap() ++ .is_empty()); ++ ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Id, None).unwrap(), ++ "lo".to_string() ++ ); ++ ++ let major = unit ++ .subst_format(FormatSubstitutionType::Major, None) ++ .unwrap(); ++ let minor = unit ++ .subst_format(FormatSubstitutionType::Minor, None) ++ .unwrap(); ++ assert_eq!(format!("{}:{}", major, minor), major_minor); ++ ++ unit.program_result = "hello world test".to_string(); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Result, None) ++ .unwrap(), ++ "hello world test".to_string() ++ ); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Result, Some("0".to_string())) ++ .unwrap(), ++ "hello".to_string() ++ ); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Result, Some("1+".to_string())) ++ .unwrap(), ++ "world test".to_string() ++ ); ++ unit.subst_format(FormatSubstitutionType::Result, Some("x".to_string())) ++ .unwrap_err(); ++ unit.subst_format(FormatSubstitutionType::Result, Some("x+".to_string())) ++ .unwrap_err(); ++ ++ assert!(unit ++ .subst_format(FormatSubstitutionType::Result, Some("3+".to_string())) ++ .unwrap() ++ .is_empty()); ++ ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Name, None) ++ .unwrap(), ++ sysname ++ ); ++ ++ unit.name = "test".to_string(); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Name, None) ++ .unwrap(), ++ "test".to_string(), ++ ); ++ ++ dev.borrow().add_devlink("test").unwrap(); ++ dev.borrow().sealed.replace(true); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Links, None) ++ .unwrap(), ++ "test".to_string(), ++ ); ++ ++ unit.subst_format(FormatSubstitutionType::Invalid, None) ++ .unwrap_err(); ++ ++ Ok(()) ++ }) ++ { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ ++ let dev = Rc::new(RefCell::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ )); ++ let unit = ExecuteUnitData::new(dev); ++ assert_eq!( ++ unit.subst_format(FormatSubstitutionType::Name, None) ++ .unwrap(), ++ "lo".to_string() ++ ); ++ } ++} +-- +2.33.0 + diff --git a/backport-test-devmaster-add-UT-for-node-and-modify-the-displa.patch b/backport-test-devmaster-add-UT-for-node-and-modify-the-displa.patch new file mode 100644 index 0000000..776f293 --- /dev/null +++ b/backport-test-devmaster-add-UT-for-node-and-modify-the-displa.patch @@ -0,0 +1,134 @@ +From e0d987db5fb3a13b07e148991af3aa8c434cb074 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 24 Nov 2023 15:09:11 +0800 +Subject: [PATCH 086/103] test(devmaster): add UT for node and modify the + displaying format of rules token + +--- + exts/devmaster/src/lib/rules/node.rs | 103 ++++++++++++++++++++++++++- + 1 file changed, 101 insertions(+), 2 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/node.rs b/exts/devmaster/src/lib/rules/node.rs +index 9fba906e..de600f72 100644 +--- a/exts/devmaster/src/lib/rules/node.rs ++++ b/exts/devmaster/src/lib/rules/node.rs +@@ -702,10 +702,11 @@ pub(crate) fn cleanup_prior_dir() -> Result<()> { + #[cfg(test)] + mod test { + use super::*; +- use basic::fs_util::is_symlink; ++ use basic::fs_util::{is_symlink, touch_file}; ++ use device::device_enumerator::*; + use device::utils::LoopDev; + use nix::unistd::unlink; +- use std::fs::{self, read_link, remove_dir, remove_dir_all}; ++ use std::fs::{self, read_link, remove_dir, remove_dir_all, remove_file}; + + #[test] + fn test_update_node() { +@@ -816,4 +817,102 @@ mod test { + } + } + } ++ ++ #[test] ++ fn test_escape_prior_dir() { ++ assert_eq!(&escape_prior_dir("aaa/bbb"), "aaa\\x2fbbb"); ++ assert_eq!(&escape_prior_dir("aaa\\bbb"), "aaa\\x5cbbb"); ++ } ++ ++ #[test] ++ fn test_get_prior_dir() { ++ assert_eq!( ++ get_prior_dir("/../xxx").unwrap_err().get_errno(), ++ nix::Error::EINVAL ++ ); ++ assert_eq!( ++ get_prior_dir("xxx").unwrap_err().get_errno(), ++ nix::Error::EINVAL ++ ); ++ } ++ ++ #[test] ++ fn test_prior_dir_read_one() { ++ if let Err(e) = ++ LoopDev::inner_process("/tmp/test_prior_dir_read_one", 1024 * 1024 * 10, |dev| { ++ let devname = dev.get_devname().unwrap(); ++ let id = dev.get_device_id().unwrap(); ++ ++ create_dir_all("/tmp/test_prior_dir_read_one_dir").unwrap(); ++ ++ let p = Path::new("/tmp/test_prior_dir_read_one_dir"); ++ ++ let dir = nix::dir::Dir::open(p, OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); ++ ++ /* Missing link priority. */ ++ symlink( ++ &format!(":{}", devname), ++ &format!("/tmp/test_prior_dir_read_one_dir/{}", id), ++ false, ++ ) ++ .unwrap(); ++ ++ prior_dir_read_one(dir.as_raw_fd(), &id).unwrap_err(); ++ ++ /* Non-existing device node path. */ ++ symlink( ++ "0:xxx", ++ &format!("/tmp/test_prior_dir_read_one_dir/{}", id), ++ false, ++ ) ++ .unwrap(); ++ ++ prior_dir_read_one(dir.as_raw_fd(), &id).unwrap_err(); ++ ++ remove_dir_all("/tmp/test_prior_dir_read_one_dir").unwrap(); ++ ++ Ok(()) ++ }) ++ { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_node_symlink() { ++ if let Err(e) = LoopDev::inner_process("/tmp/test_node_symlink", 1024 * 1024 * 10, |dev| { ++ let dev = Rc::new(RefCell::new(dev.shallow_clone().unwrap())); ++ ++ touch_file( ++ "/tmp/test_node_symlink_link", ++ false, ++ Some(0o777), ++ None, ++ None, ++ ) ++ .unwrap(); ++ ++ /* If the target exists, the symlink will not be created. */ ++ node_symlink(dev, "", "/tmp/test_node_symlink_link").unwrap_err(); ++ ++ remove_file("/tmp/test_node_symlink_link").unwrap(); ++ ++ Ok(()) ++ }) { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ ++ #[test] ++ fn test_device_get_symlink_by_devnum() { ++ let mut e = DeviceEnumerator::new(); ++ e.set_enumerator_type(DeviceEnumerationType::Devices); ++ e.add_match_subsystem("tty", true).unwrap(); ++ e.add_match_subsystem("block", true).unwrap(); ++ ++ for d in e.iter() { ++ let s = device_get_symlink_by_devnum(d).unwrap(); ++ println!("{}", s); ++ } ++ } + } +-- +2.33.0 + diff --git a/backport-test-devmaster-add-UT-for-rules-applying.patch b/backport-test-devmaster-add-UT-for-rules-applying.patch new file mode 100644 index 0000000..8735f13 --- /dev/null +++ b/backport-test-devmaster-add-UT-for-rules-applying.patch @@ -0,0 +1,947 @@ +From 9eab33e9cfaa97cac808ba5ebc3c2d2c30157681 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 22 Nov 2023 15:45:16 +0800 +Subject: [PATCH 079/103] test(devmaster): add UT for rules applying + +--- + exts/devmaster/src/lib/rules/exec_mgr.rs | 917 +++++++++++++++++++++++ + 1 file changed, 917 insertions(+) + +diff --git a/exts/devmaster/src/lib/rules/exec_mgr.rs b/exts/devmaster/src/lib/rules/exec_mgr.rs +index 66c1a06c..03da901d 100644 +--- a/exts/devmaster/src/lib/rules/exec_mgr.rs ++++ b/exts/devmaster/src/lib/rules/exec_mgr.rs +@@ -2140,8 +2140,13 @@ impl RuleLine { + + #[cfg(test)] + mod tests { ++ use std::fs::remove_file; ++ + use super::*; ++ use crate::rules::rules_load::tests::create_tmp_file; + use crate::rules::FormatSubstitutionType; ++ use device::utils::LoopDev; ++ use log::{init_log, Level}; + + #[test] + #[ignore] +@@ -2294,4 +2299,916 @@ mod tests { + assert_eq!(unit.apply_format("$parent", false).unwrap(), "sda"); + assert_eq!(unit.apply_format("$devnode", false).unwrap(), "/dev/sda1"); + } ++ ++ impl ExecuteManager { ++ #[allow(clippy::too_many_arguments)] ++ fn test_apply_one_rule_token( ++ &self, ++ key: &str, ++ attr: &str, ++ op: &str, ++ value: &str, ++ rules: Arc>, ++ rule_line: Arc>>, ++ device: Rc>, ++ ) -> Result { ++ let token = RuleToken::parse_token( ++ key.to_string(), ++ if attr.is_empty() { ++ None ++ } else { ++ Some(attr.to_string()) ++ }, ++ op.to_string(), ++ value.to_string(), ++ rules, ++ rule_line, ++ ) ++ .unwrap(); ++ *self.current_rule_token.borrow_mut() = Arc::new(RwLock::new(Some(token))); ++ self.apply_rule_token(device) ++ } ++ } ++ ++ #[test] ++ fn test_apply_rules() { ++ init_log( ++ "test_apply_rules", ++ Level::Debug, ++ vec!["console"], ++ "", ++ 0, ++ 0, ++ false, ++ ); ++ ++ let device = Rc::new(RefCell::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ )); ++ device.borrow().set_base_path("/tmp/devmaster"); ++ let rules = Arc::new(RwLock::new(Rules::new(vec![], ResolveNameTime::Early))); ++ let rule_file = Arc::new(RwLock::new(Some(RuleFile::new("".to_string())))); ++ let rule_line = Arc::new(RwLock::new(Some(RuleLine::new( ++ "".to_string(), ++ 0, ++ rule_file, ++ )))); ++ let unit = ExecuteUnit::new(device.clone()); ++ let mgr = ExecuteManager::new(Arc::new(RwLock::new(Cache::new(vec![], vec![])))); ++ *mgr.current_unit.borrow_mut() = Some(unit); ++ ++ device.borrow().set_action_from_string("change").unwrap(); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ACTION", ++ "", ++ "==", ++ "change", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "ACTION", ++ "", ++ "==", ++ "add", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "DEVPATH", ++ "", ++ "==", ++ "*lo", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "xxx", ++ "==", ++ "xxx", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ device.borrow().add_tag("xxx", true); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TAG", ++ "", ++ "==", ++ "xxx", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "SUBSYSTEM", ++ "", ++ "==", ++ "net", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TEST", ++ "444", ++ "==", ++ "[net/lo]ifindex", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TEST", ++ "644", ++ "==", ++ "queues/*/rps_cpus", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TEST", ++ "444", ++ "==", ++ "ifindex", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TEST", ++ "", ++ "!=", ++ "asfsdfa", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "TEST", ++ "444", ++ "==", ++ "$attr", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ create_tmp_file( ++ "/tmp", ++ "property", ++ "HELLO=WORLD ++GOOD=LUCK", ++ true, ++ ); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "file", ++ "==", ++ "/tmp/property", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert_eq!( ++ &device.borrow().get_property_value("HELLO").unwrap(), ++ "WORLD" ++ ); ++ ++ assert_eq!(&device.borrow().get_property_value("GOOD").unwrap(), "LUCK"); ++ ++ remove_file("/tmp/property").unwrap(); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "program", ++ "==", ++ "echo WATER=FLOW", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert_eq!( ++ &device.borrow().get_property_value("WATER").unwrap(), ++ "FLOW" ++ ); ++ ++ create_tmp_file( ++ "/tmp/devmaster/data", ++ &device.borrow().get_device_id().unwrap(), ++ "E:BLACK=PINK", ++ true, ++ ); ++ mgr.current_unit ++ .borrow() ++ .as_ref() ++ .unwrap() ++ .clone_device_db() ++ .unwrap(); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "db", ++ "==", ++ "BLACK", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert_eq!( ++ &device.borrow().get_property_value("BLACK").unwrap(), ++ "PINK" ++ ); ++ remove_file(&format!( ++ "/tmp/devmaster/data/{}", ++ device.borrow().get_device_id().unwrap() ++ )) ++ .unwrap(); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "cmdline", ++ "==", ++ "root", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(!device ++ .borrow() ++ .get_property_value("root") ++ .unwrap() ++ .is_empty()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "cmdline", ++ "!=", ++ "asdfasdf", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "PROGRAM", ++ "", ++ "==", ++ "echo $attr", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "PROGRAM", ++ "", ++ "==", ++ "cat /tmp/test_nonexist", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "PROGRAM", ++ "", ++ "==", ++ "asdfasdf", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "PROGRAM", ++ "", ++ "==", ++ "echo hello", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "RESULT", ++ "", ++ "==", ++ "hello", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "string_escape=none", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "string_escape=replace", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "db_persist", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "db_persist", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "link_priority=1", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OWNER", ++ "", ++ "=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "GROUP", ++ "", ++ "=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "MODE", ++ "", ++ "=", ++ "777", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ rules.write().unwrap().resolve_name_time = ResolveNameTime::Late; ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OWNER", ++ "", ++ "=", ++ "root", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "GROUP", ++ "", ++ "=", ++ "root", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ mgr.test_apply_one_rule_token( ++ "ENV", ++ "mode", ++ "=", ++ "777", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap(); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "MODE", ++ "", ++ "=", ++ "$env{mode}", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ rules.write().unwrap().resolve_name_time = ResolveNameTime::Early; ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OWNER", ++ "", ++ ":=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "GROUP", ++ "", ++ ":=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "MODE", ++ "", ++ ":=", ++ "777", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OWNER", ++ "", ++ "=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "GROUP", ++ "", ++ "=", ++ "0", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "MODE", ++ "", ++ "=", ++ "777", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "BLACK", ++ "=", ++ "YELLOW", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert_eq!( ++ &device.borrow().get_property_value("BLACK").unwrap(), ++ "YELLOW" ++ ); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TAG", ++ "", ++ "+=", ++ "aaa", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(device.borrow().has_tag("aaa").unwrap()); ++ assert!(device.borrow().has_current_tag("aaa").unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TAG", ++ "", ++ "-=", ++ "aaa", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(device.borrow().has_tag("aaa").unwrap()); ++ assert!(!device.borrow().has_current_tag("aaa").unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TAG", ++ "", ++ "=", ++ "bbb", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(!device.borrow().has_tag("aaa").unwrap()); ++ assert!(!device.borrow().has_current_tag("aaa").unwrap()); ++ assert!(device.borrow().has_tag("bbb").unwrap()); ++ assert!(device.borrow().has_current_tag("bbb").unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "NAME", ++ "", ++ ":=", ++ "test", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "NAME", ++ "", ++ "=", ++ "test", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ if mgr ++ .test_apply_one_rule_token( ++ "ATTR", ++ "ifalias", ++ "=", ++ "test", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .is_ok() ++ { ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ATTR", ++ "ifalias", ++ "==", ++ "test", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ } ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "RUN", ++ "builtin", ++ "+=", ++ "path_id", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "PAPER", ++ "+=", ++ "", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "PAPER", ++ "==", ++ "", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "PAPER", ++ "=", ++ "", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "PAPER", ++ "==", ++ "", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ENV", ++ "PAPER", ++ "+=", ++ "BOOK", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ assert_eq!( ++ &device.borrow().get_property_value("PAPER").unwrap(), ++ "BOOK" ++ ); ++ ++ assert!(!mgr ++ .test_apply_one_rule_token( ++ "IMPORT", ++ "builtin", ++ "==", ++ "path_id $attr", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "TAG", ++ "", ++ "+=", ++ "$env", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "NAME", ++ "", ++ "=", ++ "$env", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "ATTR", ++ "ifalias", ++ "=", ++ "$env", ++ rules.clone(), ++ rule_line.clone(), ++ device.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "RUN", ++ "", ++ "+=", ++ "$env", ++ rules.clone(), ++ rule_line.clone(), ++ device, ++ ) ++ .unwrap()); ++ ++ // /* TODO rules */ ++ // let rules_c = rules.clone(); ++ // let rule_line_c = rule_line.clone(); ++ // let device_c = device.clone(); ++ // assert!(catch_unwind(move || { ++ // let _ = mgr.test_apply_one_rule_token( ++ // "CONST", ++ // "virt", ++ // "==", ++ // "xxx", ++ // rules_c, ++ // rule_line_c, ++ // device_c, ++ // ); ++ // }) ++ // .is_err()); ++ ++ match LoopDev::new("/tmp/test_apply_rules", 1024 * 1024 * 10) { ++ Ok(lo) => { ++ let devpath = lo.get_device_path().unwrap(); ++ let dev = Rc::new(RefCell::new( ++ Device::from_path(devpath.to_str().unwrap()).unwrap(), ++ )); ++ dev.borrow().set_base_path("/tmp/devmaster"); ++ ++ dev.borrow().add_devlink("/dev/test").unwrap(); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "SYMLINK", ++ "", ++ "==", ++ "/dev/test", ++ rules.clone(), ++ rule_line.clone(), ++ dev.clone(), ++ ) ++ .unwrap()); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "SYMLINK", ++ "", ++ "!=", ++ "/dev/xxx", ++ rules.clone(), ++ rule_line.clone(), ++ dev.clone(), ++ ) ++ .unwrap()); ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "SYMLINK", ++ "", ++ "+=", ++ "xxx", ++ rules.clone(), ++ rule_line.clone(), ++ dev.clone(), ++ ) ++ .unwrap()); ++ assert!(dev.borrow().has_devlink("/dev/xxx")); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ "+=", ++ "watch", ++ rules.clone(), ++ rule_line.clone(), ++ dev.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token( ++ "OPTIONS", ++ "", ++ ":=", ++ "watch", ++ rules.clone(), ++ rule_line.clone(), ++ dev.clone(), ++ ) ++ .unwrap()); ++ ++ assert!(mgr ++ .test_apply_one_rule_token("OPTIONS", "", "=", "watch", rules, rule_line, dev,) ++ .unwrap()); ++ } ++ Err(e) => { ++ assert!(e.is_errno(nix::Error::EACCES) || e.is_errno(nix::Error::EBUSY)); ++ } ++ } ++ } + } +-- +2.33.0 + diff --git a/backport-test-devmaster-add-UT-for-rules-module.patch b/backport-test-devmaster-add-UT-for-rules-module.patch new file mode 100644 index 0000000..8fac687 --- /dev/null +++ b/backport-test-devmaster-add-UT-for-rules-module.patch @@ -0,0 +1,147 @@ +From 124af0af5a9e28d70a362463a69a5daab3b70712 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 24 Nov 2023 15:08:07 +0800 +Subject: [PATCH 085/103] test(devmaster): add UT for rules module + +--- + exts/devmaster/src/lib/rules/mod.rs | 105 ++++++++++++++++++++++++---- + 1 file changed, 90 insertions(+), 15 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/mod.rs b/exts/devmaster/src/lib/rules/mod.rs +index a88a5f99..b5ee3c7d 100644 +--- a/exts/devmaster/src/lib/rules/mod.rs ++++ b/exts/devmaster/src/lib/rules/mod.rs +@@ -218,9 +218,12 @@ impl RuleToken { + #[inline] + pub(crate) fn get_token_content(&self) -> String { + if let Some(attribute) = self.get_token_attribute() { +- format!("{}{{{}}}{}{}", self.r#type, attribute, self.op, self.value) ++ format!( ++ "{}{{{}}}{}\"{}\"", ++ self.r#type, attribute, self.op, self.value ++ ) + } else { +- format!("{}{}{}", self.r#type, self.op, self.value) ++ format!("{}{}\"{}\"", self.r#type, self.op, self.value) + } + } + } +@@ -316,7 +319,7 @@ impl Display for TokenType { + Self::MatchParentsDriver => "DRIVERS", + Self::MatchParentsAttr => "ATTRS", + Self::MatchParentsTag => "TAGS", +- Self::MatchResult => "RESULTS", ++ Self::MatchResult => "RESULT", + Self::MatchTest => "TEST", + Self::MatchProgram => "PROGRAM", + Self::MatchImportFile +@@ -567,18 +570,6 @@ bitflags! { + } + } + +-// bitflags! { +-// /// value matching type +-// pub(crate) struct MatchType: u8 { +-// /// match empty string +-// const EMPTY = 1<<0; +-// /// use shell glob parttern to match +-// const PATTERN = 1<<1; +-// /// match "subsystem", "bus", or "class" +-// const SUBSYSTEM = 1<<2; +-// } +-// } +- + /// match type + #[derive(Debug, Clone, Copy)] + #[allow(dead_code)] +@@ -744,3 +735,87 @@ pub(crate) enum EscapeType { + None, + Replace, + } ++ ++#[cfg(test)] ++mod test { ++ use log::{init_log, Level}; ++ ++ use super::*; ++ use crate::rules::rules_load::tests::create_tmp_file; ++ ++ #[test] ++ fn test_rules_display() { ++ init_log( ++ "test_rules_display", ++ Level::Debug, ++ vec!["console"], ++ "", ++ 0, ++ 0, ++ false, ++ ); ++ ++ create_tmp_file( ++ "/tmp/test_rules_display/rules.d", ++ "00-test.rules", ++ " ++ACTION==\"change\" ++DEVPATH==\"xxx\" ++KERNEL==\"xxx\" ++SYMLINK==\"xxx\" ++SYMLINK+=\"xxx\" ++NAME==\"xxx\" ++NAME=\"x\" ++ENV{x}=\"x\" ++ENV{x}==\"x\" ++CONST{virt}==\"x\" ++TAG+=\"x\" ++TAG==\"x\" ++SUBSYSTEM==\"x\" ++DRIVER==\"x\" ++ATTR{x}==\"x\" ++ATTR{x}=\"x\" ++SYSCTL{x}==\"x\" ++SYSCTL{x}=\"x\" ++KERNELS==\"x\" ++SUBSYSTEMS==\"x\" ++DRIVERS==\"x\" ++ATTRS{x}==\"x\" ++TAGS==\"x\" ++RESULT==\"x\" ++TEST==\"x\" ++PROGRAM==\"x\" ++IMPORT{file}==\"x\" ++IMPORT{program}==\"echo hello\" ++IMPORT{builtin}==\"path_id\" ++IMPORT{db}==\"x\" ++IMPORT{cmdline}==\"x\" ++IMPORT{parent}==\"x\" ++OPTIONS+=\"string_escape=none\" ++OPTIONS+=\"string_escape=replace\" ++OPTIONS+=\"db_persist\" ++OPTIONS+=\"watch\" ++OPTIONS+=\"link_priority=10\" ++OPTIONS+=\"log_level=1\" ++OPTIONS+=\"static_node=/dev/sda\" ++SECLABEL{x}+=\"x\" ++RUN{builtin}+=\"path_id\" ++RUN{program}+=\"x\" ++GOTO=\"x\" ++LABEL=\"x\" ++", ++ true, ++ ); ++ ++ let rule = Arc::new(RwLock::new(Rules::new( ++ vec!["/tmp/test_rules_display/rules.d".to_string()], ++ ResolveNameTime::Late, ++ ))); ++ ++ Rules::parse_rules(rule.clone()); ++ ++ println!("{}", rule.read().unwrap()); ++ ++ std::fs::remove_dir_all("/tmp/test_rules_display").unwrap(); ++ } ++} +-- +2.33.0 + diff --git a/backport-test-devmaster-optimize-UT-for-testing-framework-and.patch b/backport-test-devmaster-optimize-UT-for-testing-framework-and.patch new file mode 100644 index 0000000..5f7a1f9 --- /dev/null +++ b/backport-test-devmaster-optimize-UT-for-testing-framework-and.patch @@ -0,0 +1,167 @@ +From 94a3a51db76f45f8941d8a6cb649da0624fde67b Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 16 Nov 2023 11:16:12 +0800 +Subject: [PATCH 066/103] test(devmaster): optimize UT for testing framework + and add some new cases + +--- + exts/devmaster/src/bin/devctl/daemon/mod.rs | 7 ++ + exts/devmaster/src/lib/framework/job_queue.rs | 85 ++++++++++++------- + .../src/lib/framework/worker_manager.rs | 1 + + 3 files changed, 64 insertions(+), 29 deletions(-) + +diff --git a/exts/devmaster/src/bin/devctl/daemon/mod.rs b/exts/devmaster/src/bin/devctl/daemon/mod.rs +index 3e4bd96d..53931650 100644 +--- a/exts/devmaster/src/bin/devctl/daemon/mod.rs ++++ b/exts/devmaster/src/bin/devctl/daemon/mod.rs +@@ -71,6 +71,13 @@ mod test { + std::thread::sleep(std::time::Duration::from_secs(1)); + + let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ ++ /* Trigger more than the number of workers. */ ++ dev.trigger(DeviceAction::Change).unwrap(); ++ dev.trigger(DeviceAction::Change).unwrap(); ++ dev.trigger(DeviceAction::Change).unwrap(); ++ dev.trigger(DeviceAction::Change).unwrap(); ++ dev.trigger(DeviceAction::Change).unwrap(); + dev.trigger(DeviceAction::Change).unwrap(); + + /* Sleep more than 3 seconds to wait for the workers being recycled. */ +diff --git a/exts/devmaster/src/lib/framework/job_queue.rs b/exts/devmaster/src/lib/framework/job_queue.rs +index 7c6d6a8d..e488f480 100644 +--- a/exts/devmaster/src/lib/framework/job_queue.rs ++++ b/exts/devmaster/src/lib/framework/job_queue.rs +@@ -22,7 +22,6 @@ use std::{ + cell::RefCell, + cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, + collections::VecDeque, +- fmt::{self, Display}, + rc::{Rc, Weak}, + }; + +@@ -37,20 +36,8 @@ pub enum JobState { + Running, + } + +-impl Display for JobState { +- /// +- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +- let state = match self { +- JobState::Undef => "Undef", +- JobState::Queued => "Queued", +- JobState::Running => "Running", +- }; +- +- write!(f, "{}", state) +- } +-} +- + /// device job ++#[derive(Debug)] + pub struct DeviceJob { + /// internal device + pub device: Device, +@@ -168,8 +155,6 @@ impl JobQueue { + gc.close_killer(e); + } + +- // self.job_queue_show_state(); +- + for job in self.jobs.borrow().iter() { + match job.get_state() { + JobState::Queued => {} +@@ -241,19 +226,6 @@ impl JobQueue { + log::debug!("Job Queue: insert job {}", seqnum); + } + +- // /// cleanup the job queue, if match_state is Undef, cleanup all jobs, otherwise just retain the unmatched jobs +- // pub(crate) fn job_queue_cleanup(&self, match_state: JobState) { +- // self.jobs.borrow_mut().retain_mut(|job| { +- // if match_state != JobState::Undef && match_state != job.get_state() { +- // return true; +- // } +- +- // false +- // }); +- +- // log::debug!("Job Queue: cleanup"); +- // } +- + /// free a job from job queue + pub(crate) fn job_free(&self, job: &Rc) { + job.job_free(); +@@ -289,3 +261,58 @@ impl JobQueue { + self.jobs.borrow().is_empty() + } + } ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ use std::sync::{Arc, RwLock}; ++ ++ #[test] ++ fn test_job_cmp() { ++ let j1 = DeviceJob::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ JobState::Queued, ++ 1000, ++ ); ++ ++ let j2 = DeviceJob::new( ++ Device::from_subsystem_sysname("net", "lo").unwrap(), ++ JobState::Queued, ++ 1000, ++ ); ++ ++ assert_eq!(j1, j2); ++ } ++ ++ #[test] ++ fn test_job_queue() { ++ let events = Rc::new(Events::new().unwrap()); ++ let cache = Cache::new(vec![], vec![]); ++ let devmaster = Rc::new(RefCell::new(Devmaster { ++ events, ++ worker_manager: None, ++ control_manager: None, ++ monitor: None, ++ job_queue: None, ++ gc: None, ++ cache: Arc::new(RwLock::new(cache)), ++ })); ++ ++ let job_queue = JobQueue::new(devmaster); ++ ++ /* Test start job when the queue is empty. */ ++ job_queue.job_queue_start(None); ++ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ job_queue.job_queue_insert(dev); ++ ++ /* Test insert dulplicate jobs with the same seqnum. */ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ dev.set_seqnum_from_string("1000").unwrap(); ++ job_queue.job_queue_insert(dev); ++ ++ let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); ++ dev.set_seqnum_from_string("1000").unwrap(); ++ job_queue.job_queue_insert(dev); ++ } ++} +diff --git a/exts/devmaster/src/lib/framework/worker_manager.rs b/exts/devmaster/src/lib/framework/worker_manager.rs +index 870f6779..bddc9deb 100644 +--- a/exts/devmaster/src/lib/framework/worker_manager.rs ++++ b/exts/devmaster/src/lib/framework/worker_manager.rs +@@ -64,6 +64,7 @@ pub struct WorkerManager { + } + + /// worker ++#[derive(Debug)] + pub struct Worker { + /// worker unique id + id: u32, +-- +2.33.0 + diff --git a/backport-test-devmaster-test-exception-scenarios-on-parsing-r.patch b/backport-test-devmaster-test-exception-scenarios-on-parsing-r.patch new file mode 100644 index 0000000..a1e380f --- /dev/null +++ b/backport-test-devmaster-test-exception-scenarios-on-parsing-r.patch @@ -0,0 +1,71 @@ +From d661f601e4bb2b3ffcbb1db5bd11a51c6cf8abc1 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Fri, 17 Nov 2023 01:15:00 +0800 +Subject: [PATCH 068/103] test(devmaster): test exception scenarios on parsing + rules + +--- + exts/devmaster/src/lib/rules/rules_load.rs | 38 ++++++++++++++++++++++ + 1 file changed, 38 insertions(+) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index cd90c271..19b7aaa4 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -1725,10 +1725,13 @@ impl RuleToken { + #[cfg(test)] + mod tests { + use crate::config::*; ++ use basic::fs_util::touch_file; + use log::init_log; + use log::Level; + + use super::*; ++ use std::fs::create_dir_all; ++ use std::fs::remove_dir_all; + use std::{fs, path::Path, thread::JoinHandle}; + + fn create_test_rules_dir(dir: &'static str) { +@@ -2099,4 +2102,39 @@ SYMLINK += \"test111111\"", + ); + assert_eq!(token.read().unwrap().as_ref().unwrap().value, "0"); + } ++ ++ #[test] ++ fn test_parse_rules() { ++ create_dir_all("/tmp/devmaster/rules").unwrap(); ++ ++ /* Normal rule file. */ ++ touch_file( ++ "/tmp/devmaster/rules/00-a.rules", ++ false, ++ Some(0o777), ++ None, ++ None, ++ ) ++ .unwrap(); ++ /* Skip parsing the file with invalid suffix. */ ++ touch_file("/tmp/devmaster/rules/01-b", false, Some(0o777), None, None).unwrap(); ++ /* Failed to parse the file as it is not readable. */ ++ touch_file( ++ "/tmp/devmaster/rules/02-c.rules", ++ false, ++ Some(0o222), ++ None, ++ None, ++ ) ++ .unwrap(); ++ ++ let rules = Rules::new( ++ vec!["/tmp/devmaster/rules".to_string()], ++ ResolveNameTime::Never, ++ ); ++ ++ Rules::parse_rules(Arc::new(RwLock::new(rules))); ++ ++ remove_dir_all("/tmp/devmaster").unwrap(); ++ } + } +-- +2.33.0 + diff --git a/backport-test-devmaster-trigger-add-and-remove-uevent-to-cove.patch b/backport-test-devmaster-trigger-add-and-remove-uevent-to-cove.patch new file mode 100644 index 0000000..7515af5 --- /dev/null +++ b/backport-test-devmaster-trigger-add-and-remove-uevent-to-cove.patch @@ -0,0 +1,45 @@ +From e3056ffc97640c2791f73bc2aa24b0db07b9d60b Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 20 Nov 2023 14:47:22 +0800 +Subject: [PATCH 074/103] test(devmaster): trigger add and remove uevent to + cover netif renaming procedure + +The netif renaming procedure executes only when a add uevent of netif raised. + +In addition, the remove process was not covered previously. +--- + exts/devmaster/src/bin/devctl/daemon/mod.rs | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +diff --git a/exts/devmaster/src/bin/devctl/daemon/mod.rs b/exts/devmaster/src/bin/devctl/daemon/mod.rs +index 53931650..4aa957a4 100644 +--- a/exts/devmaster/src/bin/devctl/daemon/mod.rs ++++ b/exts/devmaster/src/bin/devctl/daemon/mod.rs +@@ -61,7 +61,10 @@ mod test { + + #[test] + fn test_run_daemon() { +- /* Require root privilege, skip in ci environment. */ ++ /* Require root privilege, skip in ci environment. ++ * ++ * In addition, this test case will fail when devmaster daemon is running. ++ */ + let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); + if dev.trigger(DeviceAction::Change).is_err() { + return; +@@ -73,9 +76,9 @@ mod test { + let dev = Device::from_subsystem_sysname("net", "lo").unwrap(); + + /* Trigger more than the number of workers. */ +- dev.trigger(DeviceAction::Change).unwrap(); +- dev.trigger(DeviceAction::Change).unwrap(); +- dev.trigger(DeviceAction::Change).unwrap(); ++ dev.trigger(DeviceAction::Remove).unwrap(); ++ dev.trigger(DeviceAction::Add).unwrap(); ++ dev.trigger(DeviceAction::Add).unwrap(); + dev.trigger(DeviceAction::Change).unwrap(); + dev.trigger(DeviceAction::Change).unwrap(); + dev.trigger(DeviceAction::Change).unwrap(); +-- +2.33.0 + diff --git a/backport-test-devmaster-use-unwrap-instead-of-asserting-is-ok.patch b/backport-test-devmaster-use-unwrap-instead-of-asserting-is-ok.patch new file mode 100644 index 0000000..4ee124a --- /dev/null +++ b/backport-test-devmaster-use-unwrap-instead-of-asserting-is-ok.patch @@ -0,0 +1,31 @@ +From b018bb63d87f7c25acfbfc1964102fb1af64ce94 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 23 Nov 2023 20:14:43 +0800 +Subject: [PATCH 083/103] test(devmaster): use unwrap instead of asserting is + ok + +Use unwrap method to display more about error information. +--- + exts/devmaster/src/lib/rules/rules_load.rs | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/exts/devmaster/src/lib/rules/rules_load.rs b/exts/devmaster/src/lib/rules/rules_load.rs +index b035f562..49da2d63 100644 +--- a/exts/devmaster/src/lib/rules/rules_load.rs ++++ b/exts/devmaster/src/lib/rules/rules_load.rs +@@ -1737,10 +1737,10 @@ pub(crate) mod tests { + use std::{fs, path::Path}; + + pub(crate) fn create_tmp_file(dir: &'static str, file: &str, content: &str, truncate: bool) { +- assert!(fs::create_dir_all(dir).is_ok()); ++ fs::create_dir_all(dir).unwrap(); + let s = format!("{}/{}", dir, file); + let p = Path::new(&s); +- assert!(fs::write(p, content,).is_ok()); ++ fs::write(p, content).unwrap(); + let mut f = fs::OpenOptions::new() + .write(true) + .truncate(truncate) +-- +2.33.0 + diff --git a/sysmaster.spec b/sysmaster.spec index 00b3330..80ad844 100644 --- a/sysmaster.spec +++ b/sysmaster.spec @@ -16,7 +16,7 @@ Name: sysmaster Version: 0.5.1 -Release: 3 +Release: 5 Summary: redesign and reimplement process1. License: Mulan PSL v2 @@ -26,6 +26,49 @@ Source0: %{name}-%{version}.tar.xz Patch0: backport-fix-getty-generator-delelte-double-quote-in-the-conf.patch Patch1: backport-fix-solve-two-sysmaster-problem-exit-after-do_execut.patch Patch2: backport-fix-solve-reboot-time-consuming.patch +Patch3: backport-feature-add-scsi_id.patch +Patch4: backport-feature-devctl-add-devctl-info.patch +Patch5: backport-fix-devmaster-keep-the-same-name-with-udev-control-s.patch +Patch6: backport-fix-devmaster-adjust-temporary-file-permissions.patch +Patch7: backport-fix-devmaster-append-zero-to-the-file-name.patch +Patch8: backport-fix-devmaster-fix-potential-integer-overflow-in-scsi.patch +Patch9: backport-fix-devmaster-fix-out-of-bounds-in-net_id.patch +Patch10: backport-fix-devmaster-fix-misspelt-sysattr-sas_address.patch +Patch11: backport-fix-devmaster-check-the-validity-of-kmod-context-dur.patch +Patch12: backport-feature-devmaster-Add-subcommands-for-devctl-trigger.patch +Patch13: backport-fix-return-Ok-when-the-.hwdb-and-hwdb.bin-files-cann.patch +Patch14: backport-fix-Failed-to-devctl-info-dev-or-sys.patch +Patch15: backport-fix-basic-complete-feature-dependencies.patch +Patch16: backport-fix-device-replenish-feature-dependency-of-uuid-on-b.patch +Patch17: backport-test-device-refactor-and-increase-test-line-coverage.patch +Patch18: backport-fix-basic-use-feature-to-control-procfs-compilation-.patch +Patch19: backport-test-device-compress-closures-and-add-some-unit-case.patch +Patch20: backport-fix-devmaster-drop-unnecessary-debug-trait-implement.patch +Patch21: backport-fix-device-fix-UT-privilege-error-in-ci.patch +Patch22: backport-fix-device-set-driver-subsystem-after-generating-dev.patch +Patch23: backport-fix-device-drop-unnecessary-error-throwing-out.patch +Patch24: backport-fix-device-cleanup-temporary-tag-files.patch +Patch25: backport-test-device-add-UT-for-device-enumerator.patch +Patch26: backport-test-device-add-UT-for-device-monitor.patch +Patch27: backport-feature-devmaster-add-devctl-control-subcommand-to-l.patch +Patch28: backport-refactor-devmaster-compress-unnecessary-code.patch +Patch29: backport-test-devmaster-optimize-UT-for-testing-framework-and.patch +Patch30: backport-test-devmaster-test-exception-scenarios-on-parsing-r.patch +Patch31: backport-fix-devmaster-cache-the-parsed-user-and-group.patch +Patch32: backport-fix-devmaster-drop-incorrect-preventation-on-removal.patch +Patch33: backport-fix-devmaster-CONST-can-only-take-arch-or-virt-as-at.patch +Patch34: backport-test-devmaster-add-UT-cases-for-rules-load.patch +Patch35: backport-test-devmaster-trigger-add-and-remove-uevent-to-cove.patch +Patch36: backport-fix-device-use-feature-to-avoid-loopdev-being-compil.patch +Patch37: backport-fix-devmaster-avoid-potential-test-case-failures.patch +Patch38: backport-refactor-devmaster-rename-the-create_tmp_rules-funct.patch +Patch39: backport-fix-device-keep-consistent-on-the-base-path-for-db-c.patch +Patch40: backport-test-devmaster-add-UT-for-rules-applying.patch +Patch41: backport-fix-device-avoid-panic-on-shallow-clone-when-subsyst.patch +Patch42: backport-test-devmaster-use-unwrap-instead-of-asserting-is-ok.patch +Patch43: backport-test-devmaster-add-UT-for-execute-unit.patch +Patch44: backport-test-devmaster-add-UT-for-rules-module.patch +Patch45: backport-test-devmaster-add-UT-for-node-and-modify-the-displa.patch ExclusiveArch: x86_64 aarch64 @@ -40,6 +83,10 @@ Summary: %{summary} %package -n devmaster Summary: Infrastructure of device management in userspace. BuildRequires: util-linux-devel kmod-devel +Requires: %{name}%{?_isa} = %{version}-%{release} +Requires(post): sysmaster +Requires(preun): sysmaster +Requires(postun): sysmaster %description -n devmaster This package provides the infrastructure of device management in userspace. @@ -173,16 +220,23 @@ ln -s /usr/lib/sysmaster/system/sshd.service %{buildroot}/etc/sysmaster/system/m /etc/sysmaster/system/sysinit.target.wants/devctl-trigger.service %post -n devmaster -test -f /etc/sysmaster/system/sysinit.target.wants/udevd.service && unlink /etc/sysmaster/system/sysinit.target.wants/udevd.service -test -f /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service && unlink /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service +test -f /etc/sysmaster/system/sysinit.target.wants/udevd.service && unlink /etc/sysmaster/system/sysinit.target.wants/udevd.service || : +test -f /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service && unlink /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service || : %postun -n devmaster -test -f /usr/lib/sysmaster/system/udevd.service && ln -s /usr/lib/sysmaster/system/udevd.service /etc/sysmaster/system/sysinit.target.wants/udevd.service -test -f /usr/lib/sysmaster/system/udev-trigger.service && ln -s /usr/lib/sysmaster/system/udev-trigger.service /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service +test -f /usr/lib/sysmaster/system/udevd.service && ln -s /usr/lib/sysmaster/system/udevd.service /etc/sysmaster/system/sysinit.target.wants/udevd.service || : +test -f /usr/lib/sysmaster/system/udev-trigger.service && ln -s /usr/lib/sysmaster/system/udev-trigger.service /etc/sysmaster/system/sysinit.target.wants/udev-trigger.service || : %changelog +* Thu Dec 7 2023 chenjiayi - 0.5.1-5 +- complement dependency from devmaster to sysmaster and avoid + exit with failure in post and postun process + +* Wed Dec 6 2023 chenjiayi - 0.5.1-4 +- sync patches from upstream + * Fri Nov 24 2023 zhangyao - 0.5.1-3 -- sync patchs from upstream +- sync patches from upstream * Mon Oct 30 2023 zhangyao - 0.5.1-2 - add simulate_udev.sh file