| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::{Error, anyhow}; |
| use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; |
| use fuchsia_component::server::ServiceFs; |
| use fuchsia_inspect::{Inspector, Node}; |
| use futures::stream::StreamExt; |
| use log::{info, warn}; |
| use std::fs; |
| use std::io::Read; |
| use std::path::{Path, PathBuf}; |
| use std::sync::LazyLock; |
| |
| const U64_SIZE: usize = (u64::BITS / 8) as usize; |
| const DEFAULT_VALUE: u64 = 1; |
| const FILENAME: &str = "counter"; |
| |
| static DIRECTORIES: LazyLock<Vec<&'static str>> = LazyLock::new(|| vec!["data", "cache", "tmp"]); |
| static INSPECTOR: LazyLock<Inspector> = LazyLock::new(Inspector::default); |
| |
| #[fuchsia::main(logging = true)] |
| async fn main() -> Result<(), Error> { |
| info!("Initializing and serving inspect on servicefs"); |
| let mut fs = ServiceFs::new(); |
| #[allow(clippy::collection_is_never_read)] |
| let mut inspect_nodes = Vec::new(); |
| let _inspect_server_task = |
| inspect_runtime::publish(&INSPECTOR, inspect_runtime::PublishOptions::default()); |
| |
| for dirname in &*DIRECTORIES { |
| let path = PathBuf::from(format!("/{}/{}", dirname, FILENAME)); |
| let node = INSPECTOR.root().create_child(*dirname); |
| info!("Attempting to read and write from {:?}", path); |
| process_file(&path, &node); |
| inspect_nodes.push(node); |
| } |
| |
| fs.take_and_serve_directory_handle()?; |
| Ok(fs.collect::<()>().await) |
| } |
| |
| /// Attempts to read a single u64 from the supplied path then write an incremented value back into |
| /// the same path. If any errors are encountered during reading the write will use a default |
| /// value. |
| fn process_file(path: &Path, inspect_node: &Node) { |
| let updated_value = match read_file(path) { |
| Ok(value) => { |
| info!("Successfully read a value from {:?}: {}", path, value); |
| inspect_node.record_uint("read_value", value); |
| value.wrapping_add(1) |
| } |
| Err(err) => { |
| let error_string = format!("{:?}", err); |
| warn!("Error reading previous value from {:?}: {}", path, error_string); |
| inspect_node.record_string("read_error", error_string); |
| DEFAULT_VALUE |
| } |
| }; |
| |
| match write_file(path, updated_value) { |
| Ok(()) => { |
| info!("Successfully wrote a value to {:?}: {}", path, updated_value); |
| inspect_node.record_uint("write_value", updated_value); |
| } |
| Err(err) => { |
| let error_string = format!("{:?}", err); |
| warn!("Error writing updated value to {:?}: {}", path, error_string); |
| inspect_node.record_string("write_error", error_string); |
| } |
| } |
| } |
| |
| /// Reads a single u64 from the supplied path, returning an error if the file does not exists or is |
| /// not exactly the right length. |
| fn read_file(path: &Path) -> Result<u64, Error> { |
| let mut buf = Vec::<u8>::new(); |
| let mut file = fs::File::open(path).map_err(|err| anyhow!("Error opening file: {:?}", err))?; |
| let size = |
| file.read_to_end(&mut buf).map_err(|err| anyhow!("Error reading file: {:?}", err))?; |
| match size { |
| U64_SIZE => Ok(BigEndian::read_u64(&buf)), |
| _ => Err(anyhow!("File length invalid: {}", size)), |
| } |
| } |
| |
| /// Writes the supplied u64 into the supplied path, returning any errors that are encountered. |
| fn write_file(path: &Path, value: u64) -> Result<(), Error> { |
| let mut file = |
| fs::File::create(path).map_err(|err| anyhow!("Error creating file: {:?}", err))?; |
| file.write_u64::<BigEndian>(value).map_err(|err| anyhow!("Error writing value: {:?}", err)) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use diagnostics_assertions::{AnyProperty, assert_data_tree}; |
| use tempfile::TempDir; |
| use test_case::test_case; |
| |
| fn make_path() -> (TempDir, PathBuf) { |
| let dir = TempDir::new().expect("error creating tempdir"); |
| let path = dir.path().join("testfile"); |
| (dir, path) |
| } |
| |
| fn make_file(data: Vec<u8>) -> (TempDir, PathBuf) { |
| let (dir, path) = make_path(); |
| fs::write(&path, &data).expect("error writing file"); |
| (dir, path) |
| } |
| |
| #[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4], Some(0x01020304); "valid")] |
| #[test_case(vec![0, 0, 0, 0, 1, 2, 3], None; "too short")] |
| #[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4, 5], None; "too long")] |
| fn test_read_existing_file(content: Vec<u8>, expected: Option<u64>) { |
| let (_tempdir, path) = make_file(content); |
| match expected { |
| Some(value) => assert_eq!(read_file(&path).unwrap(), value), |
| None => assert!(read_file(&path).is_err(), "read should fail"), |
| } |
| } |
| |
| #[test] |
| fn test_read_missing_file() { |
| let (_tempdir, path) = make_path(); |
| assert!(read_file(&path).is_err(), "read should fail"); |
| } |
| |
| #[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4], Some(0x01020304), 0x01020305; "valid")] |
| #[test_case(vec![255, 255, 255, 255, 255, 255, 255, 255], Some(u64::MAX), 0x0; "rollover")] |
| #[test_case(vec![0, 0, 0, 0, 1, 2], None, DEFAULT_VALUE; "content too short")] |
| #[fuchsia::test] |
| async fn test_process_existing_file(content: Vec<u8>, read: Option<u64>, write: u64) { |
| let (_tempdir, path) = make_file(content); |
| let inspector = &Inspector::default(); |
| process_file(&path, inspector.root()); |
| assert_eq!(read_file(&path).unwrap(), write); |
| match read { |
| Some(read_value) => assert_data_tree!( |
| inspector, |
| root: contains { |
| read_value: read_value, |
| write_value: write, |
| }), |
| None => assert_data_tree!( |
| inspector, |
| root: contains { |
| read_error: AnyProperty, |
| write_value: write, |
| }), |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn test_process_missing_file() { |
| let (_tempdir, path) = make_path(); |
| let inspector = &Inspector::default(); |
| process_file(&path, inspector.root()); |
| assert_eq!(read_file(&path).unwrap(), DEFAULT_VALUE); |
| assert_data_tree!( |
| inspector, |
| root: contains { |
| read_error: AnyProperty, |
| write_value: DEFAULT_VALUE, |
| }); |
| } |
| |
| #[fuchsia::test] |
| async fn test_process_invalid_directory() { |
| let inspector = &Inspector::default(); |
| process_file(&PathBuf::from("/i_dont_exist"), inspector.root()); |
| assert_data_tree!( |
| inspector, |
| root: contains { |
| read_error: AnyProperty, |
| write_error: AnyProperty, |
| }); |
| } |
| } |