clock_bound_d/
phc_utils.rs

1#[cfg_attr(any(test, feature = "test"), mockall::automock)]
2mod get_pci_slot {
3    /// Gets the PCI slot name for a given network interface name.
4    ///
5    /// # Arguments
6    ///
7    /// * `uevent_file_path` - The path of the uevent file where we lookup the PCI_SLOT_NAME.
8    pub(crate) fn get_pci_slot_name(uevent_file_path: &str) -> anyhow::Result<String> {
9        let contents = std::fs::read_to_string(uevent_file_path).map_err(|e| {
10            anyhow::anyhow!(
11                "Failed to open uevent file {:?} for PHC network interface specified: {}",
12                uevent_file_path,
13                e
14            )
15        })?;
16
17        Ok(contents
18            .lines()
19            .find_map(|line| line.strip_prefix("PCI_SLOT_NAME="))
20            .ok_or(anyhow::anyhow!(
21                "Failed to find PCI_SLOT_NAME at uevent file path {:?}",
22                uevent_file_path
23            ))?
24            .to_string())
25    }
26}
27
28#[cfg(not(any(test, feature = "test")))]
29pub(crate) use get_pci_slot::get_pci_slot_name;
30#[cfg(any(test, feature = "test"))]
31pub(crate) use mock_get_pci_slot::get_pci_slot_name;
32
33/// Gets the PHC Error Bound sysfs file path given a network interface name.
34///
35/// # Arguments
36///
37/// * `interface` - The network interface to lookup the PHC error bound path for.
38pub fn get_error_bound_sysfs_path(interface: &str) -> anyhow::Result<std::path::PathBuf> {
39    let uevent_file_path = format!("/sys/class/net/{}/device/uevent", interface);
40    let pci_slot_name = get_pci_slot_name(&uevent_file_path)?;
41    Ok(std::path::PathBuf::from(format!(
42        "/sys/bus/pci/devices/{}/phc_error_bound",
43        pci_slot_name
44    )))
45}
46
47pub struct PhcWithSysfsErrorBound {
48    sysfs_phc_error_bound_path: std::path::PathBuf,
49    phc_ref_id: u32,
50}
51
52#[cfg_attr(any(test, feature = "test"), mockall::automock)]
53impl PhcWithSysfsErrorBound {
54    pub(crate) fn new(phc_error_bound_path: std::path::PathBuf, phc_ref_id: u32) -> Self {
55        Self {
56            sysfs_phc_error_bound_path: phc_error_bound_path,
57            phc_ref_id,
58        }
59    }
60
61    pub(crate) fn read_phc_error_bound(&self) -> anyhow::Result<i64> {
62        std::fs::read_to_string(&self.sysfs_phc_error_bound_path)?
63            .trim()
64            .parse::<i64>()
65            .map_err(|e| anyhow::anyhow!("Failed to parse PHC error bound value to i64: {}", e))
66    }
67
68    pub(crate) fn get_phc_ref_id(&self) -> u32 {
69        self.phc_ref_id
70    }
71}
72
73#[cfg(test)]
74mod test {
75    use rstest::rstest;
76    use tempfile::NamedTempFile;
77
78    use super::*;
79
80    use std::io::Write;
81
82    #[rstest]
83    #[case::happy_path("PCI_SLOT_NAME=12345", "12345")]
84    #[case::happy_path_multi_line(
85        "
86oneline
87PCI_SLOT_NAME=23456
88twoline",
89        "23456"
90    )]
91    fn test_get_pci_slot_name_success(
92        #[case] file_contents_to_write: &str,
93        #[case] return_value: &str,
94    ) {
95        let mut test_uevent_file = NamedTempFile::new().expect("create mock uevent file failed");
96        test_uevent_file
97            .write_all(file_contents_to_write.as_bytes())
98            .expect("write to mock uevent file failed");
99
100        let rt = get_pci_slot::get_pci_slot_name(test_uevent_file.path().to_str().unwrap());
101        assert!(rt.is_ok());
102        assert_eq!(rt.unwrap(), return_value.to_string());
103    }
104
105    #[rstest]
106    #[case::missing_pci_slot_name("no pci slot name")]
107    fn test_get_pci_slot_name_failure(#[case] file_contents_to_write: &str) {
108        let mut test_uevent_file = NamedTempFile::new().expect("create mock uevent file failed");
109        test_uevent_file
110            .write_all(file_contents_to_write.as_bytes())
111            .expect("write to mock uevent file failed");
112
113        let rt = get_pci_slot::get_pci_slot_name(test_uevent_file.path().to_str().unwrap());
114        assert!(rt.is_err());
115        assert!(rt
116            .unwrap_err()
117            .to_string()
118            .contains("Failed to find PCI_SLOT_NAME at uevent file path"));
119    }
120
121    #[test]
122    fn test_get_pci_slot_name_file_does_not_exist() {
123        let rt = get_pci_slot::get_pci_slot_name("/does/not/exist");
124        assert!(rt.is_err());
125    }
126
127    #[rstest]
128    #[case::happy_path("12345", 12345)]
129    fn test_read_phc_error_bound_success(
130        #[case] file_contents_to_write: &str,
131        #[case] return_value: i64,
132    ) {
133        let mut test_phc_error_bound_file =
134            NamedTempFile::new().expect("create mock phc error bound file failed");
135        test_phc_error_bound_file
136            .write_all(file_contents_to_write.as_bytes())
137            .expect("write to mock phc error bound file failed");
138
139        let phc_error_bound_reader =
140            PhcWithSysfsErrorBound::new(test_phc_error_bound_file.path().to_path_buf(), 0);
141        let rt = phc_error_bound_reader.read_phc_error_bound();
142        assert!(rt.is_ok());
143        assert_eq!(rt.unwrap(), return_value);
144    }
145
146    #[rstest]
147    #[case::parsing_fail("asdf_not_an_i64")]
148    fn test_read_phc_error_bound_bad_file_contents(#[case] file_contents_to_write: &str) {
149        let mut test_phc_error_bound_file =
150            NamedTempFile::new().expect("create mock phc error bound file failed");
151        test_phc_error_bound_file
152            .write_all(file_contents_to_write.as_bytes())
153            .expect("write to mock phc error bound file failed");
154
155        let phc_error_bound_reader =
156            PhcWithSysfsErrorBound::new(test_phc_error_bound_file.path().to_path_buf(), 0);
157        let rt = phc_error_bound_reader.read_phc_error_bound();
158        assert!(rt.is_err());
159        assert!(rt
160            .unwrap_err()
161            .to_string()
162            .contains("Failed to parse PHC error bound value to i64"));
163    }
164
165    #[test]
166    fn test_read_phc_error_bound_file_does_not_exist() {
167        let phc_error_bound_reader = PhcWithSysfsErrorBound::new("/does/not/exist".into(), 0);
168        let rt = phc_error_bound_reader.read_phc_error_bound();
169        assert!(rt.is_err());
170    }
171
172    #[test]
173    fn test_get_phc_ref_id() {
174        let phc_error_bound_reader = PhcWithSysfsErrorBound::new("/does/not/matter".into(), 12345);
175        assert_eq!(phc_error_bound_reader.get_phc_ref_id(), 12345);
176    }
177
178    #[test]
179    fn test_get_error_bound_sysfs_path() {
180        let ctx = mock_get_pci_slot::get_pci_slot_name_context();
181        ctx.expect().returning(|_| Ok("12345".to_string()));
182        let rt = get_error_bound_sysfs_path("arbitrary_interface");
183        assert!(rt.is_ok());
184        assert_eq!(
185            rt.unwrap().to_str().unwrap(),
186            "/sys/bus/pci/devices/12345/phc_error_bound"
187        );
188    }
189}